Line data Source code
1 : /*
2 : Copyright 2018-2024, Barcelona Supercomputing Center (BSC), Spain
3 : Copyright 2015-2024, Johannes Gutenberg Universitaet Mainz, Germany
4 :
5 : This software was partially supported by the
6 : EC H2020 funded project NEXTGenIO (Project ID: 671951, www.nextgenio.eu).
7 :
8 : This software was partially supported by the
9 : ADA-FS project under the SPPEXA project funded by the DFG.
10 :
11 : This file is part of GekkoFS.
12 :
13 : GekkoFS is free software: you can redistribute it and/or modify
14 : it under the terms of the GNU General Public License as published by
15 : the Free Software Foundation, either version 3 of the License, or
16 : (at your option) any later version.
17 :
18 : GekkoFS is distributed in the hope that it will be useful,
19 : but WITHOUT ANY WARRANTY; without even the implied warranty of
20 : MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 : GNU General Public License for more details.
22 :
23 : You should have received a copy of the GNU General Public License
24 : along with GekkoFS. If not, see <https://www.gnu.org/licenses/>.
25 :
26 : SPDX-License-Identifier: GPL-3.0-or-later
27 : */
28 :
29 :
30 : #include <common/statistics/stats.hpp>
31 :
32 : using namespace std;
33 :
34 : namespace gkfs::utils {
35 :
36 : #ifdef GKFS_ENABLE_PROMETHEUS
37 : static std::string
38 33 : GetHostName() {
39 33 : char hostname[1024];
40 :
41 33 : if(::gethostname(hostname, sizeof(hostname))) {
42 0 : return {};
43 : }
44 33 : return hostname;
45 : }
46 : #endif
47 :
48 : void
49 33 : Stats::setup_Prometheus(const std::string& gateway_ip,
50 : const std::string& gateway_port) {
51 : // Prometheus Push model. Gateway
52 : #ifdef GKFS_ENABLE_PROMETHEUS
53 33 : const auto labels = Gateway::GetInstanceLabel(GetHostName());
54 33 : gateway = std::make_shared<Gateway>(gateway_ip, gateway_port, "GekkoFS",
55 33 : labels);
56 :
57 33 : registry = std::make_shared<Registry>();
58 99 : family_counter = &BuildCounter()
59 66 : .Name("IOPS")
60 66 : .Help("Number of IOPS")
61 33 : .Register(*registry);
62 :
63 231 : for(auto e : all_IopsOp) {
64 198 : iops_prometheus[e] = &family_counter->Add(
65 594 : {{"operation", IopsOp_s[static_cast<int>(e)]}});
66 : }
67 :
68 99 : family_summary = &BuildSummary()
69 66 : .Name("SIZE")
70 66 : .Help("Size of OPs")
71 33 : .Register(*registry);
72 :
73 99 : for(auto e : all_SizeOp) {
74 66 : size_prometheus[e] = &family_summary->Add(
75 66 : {{"operation", SizeOp_s[static_cast<int>(e)]}},
76 198 : Summary::Quantiles{});
77 : }
78 :
79 99 : gateway->RegisterCollectable(registry);
80 : #endif /// GKFS_ENABLE_PROMETHEUS
81 33 : }
82 :
83 33 : Stats::Stats(bool enable_chunkstats, bool enable_prometheus,
84 : const std::string& stats_file,
85 33 : const std::string& prometheus_gateway)
86 : : enable_prometheus_(enable_prometheus),
87 363 : enable_chunkstats_(enable_chunkstats) {
88 :
89 : // Init clocks
90 33 : start = std::chrono::steady_clock::now();
91 :
92 : // To simplify the control we add an element into the different maps
93 : // Statistaclly will be negligible... and we get a faster flow
94 :
95 231 : for(auto e : all_IopsOp) {
96 198 : iops_mean[e] = 0;
97 198 : time_iops[e].push_back(std::chrono::steady_clock::now());
98 : }
99 :
100 99 : for(auto e : all_SizeOp) {
101 66 : size_mean[e] = 0;
102 66 : time_size[e].push_back(pair(std::chrono::steady_clock::now(), 0.0));
103 : }
104 :
105 : #ifdef GKFS_ENABLE_PROMETHEUS
106 33 : auto pos_separator = prometheus_gateway.find(':');
107 66 : setup_Prometheus(prometheus_gateway.substr(0, pos_separator),
108 33 : prometheus_gateway.substr(pos_separator + 1));
109 : #endif
110 :
111 33 : if(!stats_file.empty() || enable_prometheus_) {
112 33 : output_thread_ = true;
113 66 : t_output = std::thread([this, stats_file] {
114 33 : output(std::chrono::duration(10s), stats_file);
115 99 : });
116 : }
117 33 : }
118 :
119 99 : Stats::~Stats() {
120 33 : if(output_thread_) {
121 33 : running = false;
122 33 : if(t_output.joinable())
123 33 : t_output.join();
124 : }
125 33 : }
126 :
127 : void
128 74 : Stats::add_read(const std::string& path, unsigned long long chunk) {
129 74 : chunk_reads[pair(path, chunk)]++;
130 74 : }
131 :
132 : void
133 100 : Stats::add_write(const std::string& path, unsigned long long chunk) {
134 100 : chunk_writes[pair(path, chunk)]++;
135 100 : }
136 :
137 :
138 : void
139 0 : Stats::output_map(std::ofstream& output) {
140 : // Ordering
141 0 : map<unsigned int, std::set<pair<std::string, unsigned long long>>>
142 0 : order_write;
143 :
144 0 : map<unsigned int, std::set<pair<std::string, unsigned long long>>>
145 0 : order_read;
146 :
147 0 : for(const auto& i : chunk_reads) {
148 0 : order_read[i.second].insert(i.first);
149 : }
150 :
151 0 : for(const auto& i : chunk_writes) {
152 0 : order_write[i.second].insert(i.first);
153 : }
154 :
155 0 : auto chunkMap =
156 0 : [](std::string caption,
157 : map<unsigned int,
158 : std::set<pair<std::string, unsigned long long>>>& order,
159 : std::ofstream& output) {
160 0 : output << caption << std::endl;
161 0 : for(auto k : order) {
162 0 : output << k.first << " -- ";
163 0 : for(auto v : k.second) {
164 0 : output << v.first << " // " << v.second << endl;
165 : }
166 : }
167 0 : };
168 :
169 0 : chunkMap("READ CHUNK MAP", order_read, output);
170 0 : chunkMap("WRITE CHUNK MAP", order_write, output);
171 0 : }
172 :
173 : void
174 2518 : Stats::add_value_iops(enum IopsOp iop) {
175 2518 : iops_mean[iop]++;
176 2518 : auto now = std::chrono::steady_clock::now();
177 :
178 2518 : const std::lock_guard<std::mutex> lock(time_iops_mutex);
179 2518 : if((now - time_iops[iop].front()) > std::chrono::duration(10s)) {
180 2 : time_iops[iop].pop_front();
181 2516 : } else if(time_iops[iop].size() >= gkfs::config::stats::max_stats)
182 0 : time_iops[iop].pop_front();
183 :
184 2518 : time_iops[iop].push_back(std::chrono::steady_clock::now());
185 : #ifdef GKFS_ENABLE_PROMETHEUS
186 2518 : if(enable_prometheus_) {
187 0 : iops_prometheus[iop]->Increment();
188 : }
189 : #endif
190 2518 : }
191 :
192 : void
193 69 : Stats::add_value_size(enum SizeOp iop, unsigned long long value) {
194 69 : auto now = std::chrono::steady_clock::now();
195 69 : size_mean[iop] += value;
196 69 : const std::lock_guard<std::mutex> lock(size_iops_mutex);
197 69 : if((now - time_size[iop].front().first) > std::chrono::duration(10s)) {
198 0 : time_size[iop].pop_front();
199 69 : } else if(time_size[iop].size() >= gkfs::config::stats::max_stats)
200 0 : time_size[iop].pop_front();
201 :
202 69 : time_size[iop].push_back(pair(std::chrono::steady_clock::now(), value));
203 : #ifdef GKFS_ENABLE_PROMETHEUS
204 69 : if(enable_prometheus_) {
205 0 : size_prometheus[iop]->Observe(value);
206 : }
207 : #endif
208 69 : if(iop == SizeOp::read_size)
209 28 : add_value_iops(IopsOp::iops_read);
210 41 : else if(iop == SizeOp::write_size)
211 41 : add_value_iops(IopsOp::iops_write);
212 69 : }
213 :
214 : /**
215 : * @brief Get the total mean value of the asked stat
216 : * This can be provided inmediately without cost
217 : * @return mean value
218 : */
219 : double
220 70 : Stats::get_mean(enum SizeOp sop) {
221 70 : auto now = std::chrono::steady_clock::now();
222 70 : auto duration =
223 70 : std::chrono::duration_cast<std::chrono::seconds>(now - start);
224 70 : double value = static_cast<double>(size_mean[sop]) /
225 70 : static_cast<double>(duration.count());
226 70 : return value;
227 : }
228 :
229 : double
230 210 : Stats::get_mean(enum IopsOp iop) {
231 210 : auto now = std::chrono::steady_clock::now();
232 210 : auto duration =
233 210 : std::chrono::duration_cast<std::chrono::seconds>(now - start);
234 210 : double value = static_cast<double>(iops_mean[iop]) /
235 210 : static_cast<double>(duration.count());
236 210 : return value;
237 : }
238 :
239 :
240 : std::vector<double>
241 70 : Stats::get_four_means(enum SizeOp sop) {
242 70 : std::vector<double> results = {0, 0, 0, 0};
243 70 : auto now = std::chrono::steady_clock::now();
244 70 : const std::lock_guard<std::mutex> lock(size_iops_mutex);
245 140 : for(auto e : time_size[sop]) {
246 70 : auto duration =
247 70 : std::chrono::duration_cast<std::chrono::minutes>(now - e.first)
248 70 : .count();
249 70 : if(duration > 10)
250 : break;
251 :
252 70 : results[3] += e.second;
253 70 : if(duration > 5)
254 0 : continue;
255 70 : results[2] += e.second;
256 70 : if(duration > 1)
257 0 : continue;
258 70 : results[1] += e.second;
259 : }
260 : // Mean in MB/s
261 70 : results[0] = get_mean(sop) / (1024.0 * 1024.0);
262 70 : results[3] /= 10 * 60 * (1024.0 * 1024.0);
263 70 : results[2] /= 5 * 60 * (1024.0 * 1024.0);
264 70 : results[1] /= 60 * (1024.0 * 1024.0);
265 :
266 140 : return results;
267 : }
268 :
269 :
270 : std::vector<double>
271 210 : Stats::get_four_means(enum IopsOp iop) {
272 210 : std::vector<double> results = {0, 0, 0, 0};
273 210 : auto now = std::chrono::steady_clock::now();
274 210 : const std::lock_guard<std::mutex> lock(time_iops_mutex);
275 2519 : for(auto e : time_iops[iop]) {
276 2309 : auto duration =
277 2309 : std::chrono::duration_cast<std::chrono::minutes>(now - e)
278 2309 : .count();
279 2309 : if(duration > 10)
280 : break;
281 :
282 2309 : results[3]++;
283 2309 : if(duration > 5)
284 0 : continue;
285 2309 : results[2]++;
286 2309 : if(duration > 1)
287 0 : continue;
288 2309 : results[1]++;
289 : }
290 :
291 210 : results[0] = get_mean(iop);
292 210 : results[3] /= 10 * 60;
293 210 : results[2] /= 5 * 60;
294 210 : results[1] /= 60;
295 :
296 420 : return results;
297 : }
298 :
299 : void
300 35 : Stats::dump(std::ofstream& of) {
301 245 : for(auto e : all_IopsOp) {
302 420 : auto tmp = get_four_means(e);
303 :
304 210 : of << "Stats " << IopsOp_s[static_cast<int>(e)]
305 420 : << " IOPS/s (avg, 1 min, 5 min, 10 min) \t\t";
306 1050 : for(auto mean : tmp) {
307 840 : of << std::setprecision(4) << std::setw(9) << mean << " - ";
308 : }
309 210 : of << std::endl;
310 : }
311 105 : for(auto e : all_SizeOp) {
312 140 : auto tmp = get_four_means(e);
313 :
314 70 : of << "Stats " << SizeOp_s[static_cast<int>(e)]
315 140 : << " MB/s (avg, 1 min, 5 min, 10 min) \t\t";
316 350 : for(auto mean : tmp) {
317 280 : of << std::setprecision(4) << std::setw(9) << mean << " - ";
318 : }
319 70 : of << std::endl;
320 : }
321 35 : of << std::endl;
322 35 : }
323 : void
324 33 : Stats::output(std::chrono::seconds d, std::string file_output) {
325 33 : int times = 0;
326 66 : std::optional<std::ofstream> of;
327 33 : if(!file_output.empty())
328 33 : of = std::ofstream(file_output, std::ios_base::openmode::_S_trunc);
329 :
330 68 : while(running) {
331 35 : if(of)
332 35 : dump(of.value());
333 35 : std::chrono::seconds a = 0s;
334 :
335 35 : times++;
336 :
337 35 : if(enable_chunkstats_ && of) {
338 35 : if(times % 4 == 0)
339 0 : output_map(of.value());
340 : }
341 : #ifdef GKFS_ENABLE_PROMETHEUS
342 35 : if(enable_prometheus_) {
343 0 : gateway->Push();
344 : }
345 : #endif
346 229 : while(running && a < d) {
347 194 : a += 1s;
348 194 : std::this_thread::sleep_for(1s);
349 : }
350 : }
351 33 : }
352 :
353 : } // namespace gkfs::utils
|