]>
Commit | Line | Data |
---|---|---|
31f18b77 FG |
1 | // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- |
2 | // vim: ts=8 sw=2 smarttab | |
3 | /* | |
4 | * Ceph - scalable distributed file system | |
5 | * | |
6 | * Copyright (C) 2017 OVH | |
7 | * | |
8 | * This is free software; you can redistribute it and/or | |
9 | * modify it under the terms of the GNU Lesser General Public | |
10 | * License version 2.1, as published by the Free Software | |
11 | * Foundation. See file COPYING. | |
12 | * | |
13 | */ | |
14 | ||
15 | #ifndef CEPH_COMMON_PERF_HISTOGRAM_H | |
16 | #define CEPH_COMMON_PERF_HISTOGRAM_H | |
17 | ||
18 | #include "common/Formatter.h" | |
19 | #include "include/int_types.h" | |
20 | ||
21 | #include <array> | |
22 | #include <atomic> | |
23 | #include <memory> | |
11fdf7f2 TL |
24 | |
25 | #include "include/ceph_assert.h" | |
31f18b77 FG |
26 | |
27 | class PerfHistogramCommon { | |
28 | public: | |
29 | enum scale_type_d : uint8_t { | |
30 | SCALE_LINEAR = 1, | |
31 | SCALE_LOG2 = 2, | |
32 | }; | |
33 | ||
34 | struct axis_config_d { | |
35 | const char *m_name = nullptr; | |
36 | scale_type_d m_scale_type = SCALE_LINEAR; | |
37 | int64_t m_min = 0; | |
38 | int64_t m_quant_size = 0; | |
39 | int32_t m_buckets = 0; | |
40 | axis_config_d() = default; | |
41 | axis_config_d(const char* name, | |
42 | scale_type_d scale_type, | |
43 | int64_t min, | |
44 | int64_t quant_size, | |
45 | int32_t buckets) | |
46 | : m_name(name), | |
47 | m_scale_type(scale_type), | |
48 | m_min(min), | |
49 | m_quant_size(quant_size), | |
50 | m_buckets(buckets) | |
51 | {} | |
52 | }; | |
53 | ||
54 | protected: | |
55 | /// Dump configuration of one axis to a formatter | |
56 | static void dump_formatted_axis(ceph::Formatter *f, const axis_config_d &ac); | |
57 | ||
58 | /// Quantize given value and convert to bucket number on given axis | |
59 | static int64_t get_bucket_for_axis(int64_t value, const axis_config_d &ac); | |
60 | ||
61 | /// Calculate inclusive ranges of axis values for each bucket on that axis | |
62 | static std::vector<std::pair<int64_t, int64_t>> get_axis_bucket_ranges( | |
63 | const axis_config_d &ac); | |
64 | }; | |
65 | ||
66 | /// PerfHistogram does trace a histogram of input values. It's an extended | |
67 | /// version of a standard histogram which does trace characteristics of a single | |
68 | /// one value only. In this implementation, values can be traced in multiple | |
69 | /// dimensions - i.e. we can create a histogram of input request size (first | |
70 | /// dimension) and processing latency (second dimension). Creating standard | |
71 | /// histogram out of such multidimensional one is trivial and requires summing | |
72 | /// values across dimensions we're not interested in. | |
73 | template <int DIM = 2> | |
74 | class PerfHistogram : public PerfHistogramCommon { | |
75 | public: | |
76 | /// Initialize new histogram object | |
77 | PerfHistogram(std::initializer_list<axis_config_d> axes_config) { | |
11fdf7f2 | 78 | ceph_assert(axes_config.size() == DIM && |
31f18b77 FG |
79 | "Invalid number of axis configuration objects"); |
80 | ||
81 | int i = 0; | |
82 | for (const auto &ac : axes_config) { | |
11fdf7f2 TL |
83 | ceph_assertf(ac.m_buckets > 0, |
84 | "Must have at least one bucket on axis"); | |
85 | ceph_assertf(ac.m_quant_size > 0, | |
86 | "Quantization unit must be non-zero positive integer value"); | |
31f18b77 FG |
87 | |
88 | m_axes_config[i++] = ac; | |
89 | } | |
90 | ||
91 | m_rawData.reset(new std::atomic<uint64_t>[get_raw_size()]); | |
92 | } | |
93 | ||
94 | /// Copy from other histogram object | |
95 | PerfHistogram(const PerfHistogram &other) | |
96 | : m_axes_config(other.m_axes_config) { | |
97 | int64_t size = get_raw_size(); | |
98 | m_rawData.reset(new std::atomic<uint64_t>[size]); | |
99 | for (int64_t i = 0; i < size; i++) { | |
100 | m_rawData[i] = other.m_rawData[i]; | |
101 | } | |
102 | } | |
103 | ||
104 | /// Set all histogram values to 0 | |
105 | void reset() { | |
106 | auto size = get_raw_size(); | |
107 | for (auto i = size; --i >= 0;) { | |
108 | m_rawData[i] = 0; | |
109 | } | |
110 | } | |
111 | ||
112 | /// Increase counter for given axis values by one | |
113 | template <typename... T> | |
114 | void inc(T... axis) { | |
115 | auto index = get_raw_index_for_value(axis...); | |
116 | m_rawData[index] += 1; | |
117 | } | |
118 | ||
119 | /// Increase counter for given axis buckets by one | |
120 | template <typename... T> | |
121 | void inc_bucket(T... bucket) { | |
122 | auto index = get_raw_index_for_bucket(bucket...); | |
123 | m_rawData[index] += 1; | |
124 | } | |
125 | ||
126 | /// Read value from given bucket | |
127 | template <typename... T> | |
128 | uint64_t read_bucket(T... bucket) const { | |
129 | auto index = get_raw_index_for_bucket(bucket...); | |
130 | return m_rawData[index]; | |
131 | } | |
132 | ||
133 | /// Dump data to a Formatter object | |
134 | void dump_formatted(ceph::Formatter *f) const { | |
135 | // Dump axes configuration | |
136 | f->open_array_section("axes"); | |
137 | for (auto &ac : m_axes_config) { | |
138 | dump_formatted_axis(f, ac); | |
139 | } | |
140 | f->close_section(); | |
141 | ||
142 | // Dump histogram values | |
143 | dump_formatted_values(f); | |
144 | } | |
145 | ||
146 | protected: | |
147 | /// Raw data stored as linear space, internal indexes are calculated on | |
148 | /// demand. | |
149 | std::unique_ptr<std::atomic<uint64_t>[]> m_rawData; | |
150 | ||
151 | /// Configuration of axes | |
152 | std::array<axis_config_d, DIM> m_axes_config; | |
153 | ||
154 | /// Dump histogram counters to a formatter | |
155 | void dump_formatted_values(ceph::Formatter *f) const { | |
156 | visit_values([f](int) { f->open_array_section("values"); }, | |
157 | [f](int64_t value) { f->dump_unsigned("value", value); }, | |
158 | [f](int) { f->close_section(); }); | |
159 | } | |
160 | ||
161 | /// Get number of all histogram counters | |
162 | int64_t get_raw_size() { | |
163 | int64_t ret = 1; | |
164 | for (const auto &ac : m_axes_config) { | |
165 | ret *= ac.m_buckets; | |
166 | } | |
167 | return ret; | |
168 | } | |
169 | ||
170 | /// Calculate m_rawData index from axis values | |
171 | template <typename... T> | |
172 | int64_t get_raw_index_for_value(T... axes) const { | |
173 | static_assert(sizeof...(T) == DIM, "Incorrect number of arguments"); | |
174 | return get_raw_index_internal<0>(get_bucket_for_axis, 0, axes...); | |
175 | } | |
176 | ||
177 | /// Calculate m_rawData index from axis bucket numbers | |
178 | template <typename... T> | |
179 | int64_t get_raw_index_for_bucket(T... buckets) const { | |
180 | static_assert(sizeof...(T) == DIM, "Incorrect number of arguments"); | |
181 | return get_raw_index_internal<0>( | |
182 | [](int64_t bucket, const axis_config_d &ac) { | |
11fdf7f2 TL |
183 | ceph_assertf(bucket >= 0, "Bucket index can not be negative"); |
184 | ceph_assertf(bucket < ac.m_buckets, "Bucket index too large"); | |
31f18b77 FG |
185 | return bucket; |
186 | }, | |
187 | 0, buckets...); | |
188 | } | |
189 | ||
190 | template <int level = 0, typename F, typename... T> | |
191 | int64_t get_raw_index_internal(F bucket_evaluator, int64_t startIndex, | |
192 | int64_t value, T... tail) const { | |
193 | static_assert(level + 1 + sizeof...(T) == DIM, | |
194 | "Internal consistency check"); | |
195 | auto &ac = m_axes_config[level]; | |
196 | auto bucket = bucket_evaluator(value, ac); | |
197 | return get_raw_index_internal<level + 1>( | |
198 | bucket_evaluator, ac.m_buckets * startIndex + bucket, tail...); | |
199 | } | |
200 | ||
201 | template <int level, typename F> | |
202 | int64_t get_raw_index_internal(F, int64_t startIndex) const { | |
203 | static_assert(level == DIM, "Internal consistency check"); | |
204 | return startIndex; | |
205 | } | |
206 | ||
207 | /// Visit all histogram counters, call onDimensionEnter / onDimensionLeave | |
208 | /// when starting / finishing traversal | |
209 | /// on given axis, call onValue when dumping raw histogram counter value. | |
210 | template <typename FDE, typename FV, typename FDL> | |
211 | void visit_values(FDE onDimensionEnter, FV onValue, FDL onDimensionLeave, | |
212 | int level = 0, int startIndex = 0) const { | |
213 | if (level == DIM) { | |
214 | onValue(m_rawData[startIndex]); | |
215 | return; | |
216 | } | |
217 | ||
218 | onDimensionEnter(level); | |
219 | auto &ac = m_axes_config[level]; | |
220 | startIndex *= ac.m_buckets; | |
221 | for (int32_t i = 0; i < ac.m_buckets; ++i, ++startIndex) { | |
222 | visit_values(onDimensionEnter, onValue, onDimensionLeave, level + 1, | |
223 | startIndex); | |
224 | } | |
225 | onDimensionLeave(level); | |
226 | } | |
227 | }; | |
228 | ||
229 | #endif |