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