bin: consolidate clients
[m6w6/libmemcached] / src / bin / memstat.cc
1 /*
2 +--------------------------------------------------------------------+
3 | libmemcached - C/C++ Client Library for memcached |
4 +--------------------------------------------------------------------+
5 | Redistribution and use in source and binary forms, with or without |
6 | modification, are permitted under the terms of the BSD license. |
7 | You should have received a copy of the license in a bundled file |
8 | named LICENSE; in case you did not receive a copy you can review |
9 | the terms online at: https://opensource.org/licenses/BSD-3-Clause |
10 +--------------------------------------------------------------------+
11 | Copyright (c) 2006-2014 Brian Aker https://datadifferential.com/ |
12 | Copyright (c) 2020 Michael Wallner <mike@php.net> |
13 +--------------------------------------------------------------------+
14 */
15
16 #include "mem_config.h"
17
18 #define PROGRAM_NAME "memstat"
19 #define PROGRAM_DESCRIPTION "Print stats/version of or analyze a memcached cluster."
20 #define PROGRAM_VERSION "1.1"
21
22 #include "common/options.hpp"
23 #include "common/checks.hpp"
24 #include "common/utilities.h"
25
26 #include <cstdio>
27 #include <chrono>
28 #include <iomanip>
29
30 static memcached_return_t print_server_version(const memcached_st *,
31 const memcached_instance_st *instance, void *) {
32 std::cerr << memcached_server_name(instance) << ":" << memcached_server_port(instance) << " "
33 << int(memcached_server_major_version(instance)) << "."
34 << int(memcached_server_minor_version(instance)) << "."
35 << int(memcached_server_micro_version(instance)) << std::endl;
36
37 return MEMCACHED_SUCCESS;
38 }
39
40 static void print_report(memcached_st *memc, memcached_analysis_st *report) {
41 uint32_t server_count = memcached_server_count(memc);
42 auto most_consumed_server = memcached_server_instance_by_position(memc, report->most_consumed_server);
43 auto least_free_server = memcached_server_instance_by_position(memc, report->least_free_server);
44 auto oldest_server = memcached_server_instance_by_position(memc, report->oldest_server);
45
46 printf("Memcached Cluster Analysis Report\n\n");
47 printf("\tNumber of Servers Analyzed : %u\n", server_count);
48 printf("\tAverage Item Size (incl/overhead) : %u bytes\n", report->average_item_size);
49
50 if (server_count == 1) {
51 printf("\nFor a detailed report, you must supply multiple servers.\n");
52 return;
53 }
54
55 printf("\n");
56 printf("\tNode with most memory consumption : %s:%u (%llu bytes)\n",
57 memcached_server_name(most_consumed_server),
58 (uint32_t) memcached_server_port(most_consumed_server),
59 (unsigned long long) report->most_used_bytes);
60 printf("\tNode with least free space : %s:%u (%llu bytes remaining)\n",
61 memcached_server_name(least_free_server),
62 (uint32_t) memcached_server_port(least_free_server),
63 (unsigned long long) report->least_remaining_bytes);
64 printf("\tNode with longest uptime : %s:%u (%us)\n",
65 memcached_server_name(oldest_server), (uint32_t) memcached_server_port(oldest_server),
66 report->longest_uptime);
67 printf("\tPool-wide Hit Ratio : %1.f%%\n", report->pool_hit_ratio);
68 printf("\n");
69 }
70
71 static bool analyze_stat(const client_options &opt, memcached_st *memc, memcached_stat_st *stat) {
72 memcached_return_t rc;
73 auto report = memcached_analyze(memc, stat, &rc);
74
75 if (rc != MEMCACHED_SUCCESS || !report) {
76 if (!opt.isset("quiet")) {
77 std::cerr << "Failure to analyze servers:" << memcached_strerror(memc, rc) << ".\n";
78 }
79 return false;
80 }
81 print_report(memc, report);
82 free(report);
83 return true;
84 }
85
86 using time_clock = std::chrono::high_resolution_clock;
87 using time_point = std::chrono::time_point<time_clock>;
88 using time_format = std::chrono::duration<double, std::ratio<1,1>>;
89 using time_format_ms = std::chrono::duration<double, std::ratio<1,1000>>;
90
91 static void latency_test(uint32_t iterations, std::vector<memcached_st> &servers) {
92 const char *test_key = "libmemcached_test_key";
93 size_t test_key_len = strlen(test_key);
94 const memcached_instance_st *slowest_server = nullptr;
95 time_point::duration slowest_time{};
96 std::vector<const memcached_instance_st *> failed_servers{};
97
98 std::cout << "Network Latency Test:\n\n" << std::showpoint << std::fixed << std::setprecision(3);
99
100 for (auto &memc : servers) {
101 memcached_return_t rc = memcached_last_error(&memc);
102
103 auto start = time_clock::now();
104 for (auto i = 0u; i < iterations; ++i) {
105 free(memcached_get(&memc, test_key, test_key_len, nullptr, nullptr, &rc));
106 if (memcached_fatal(rc)) {
107 break;
108 }
109 }
110 auto elapsed = time_clock::now() - start;
111
112 auto inst = memcached_server_instance_by_position(&memc, 0);
113 std::cout << "\t " << memcached_server_name(inst)
114 << " (" << memcached_server_port(inst) << ") ";
115
116 if (memcached_fatal(rc)) {
117 std::cout << " => failed to reach the server\n";
118 failed_servers.push_back(inst);
119 } else {
120 std::cout << " => "
121 << time_format(elapsed/iterations).count() << " seconds ("
122 << time_format_ms(elapsed/iterations).count() << "ms)\n";
123 if (slowest_time == time_point::duration::zero() || slowest_time < elapsed) {
124 slowest_time = elapsed;
125 slowest_server = inst;
126 }
127 }
128 }
129
130 if (servers.size() > 1 && slowest_server) {
131 std::cout << "\n---\n\nSlowest Server: "
132 << memcached_server_name(slowest_server) << "("
133 << memcached_server_port(slowest_server) << ")"
134 << " => "
135 << time_format(slowest_time/iterations).count() << " seconds ("
136 << time_format_ms(slowest_time/iterations).count() << "ms)\n";
137 }
138 if (!failed_servers.empty()) {
139 for (const auto inst : failed_servers) {
140 std::cout << "Failed Server: " << memcached_server_name(inst)
141 << " (" << memcached_server_port(inst)
142 << ") => " << memcached_strerror(inst->root, memcached_server_error_return(inst))
143 << "\n";
144 }
145 }
146 }
147
148 static bool analyze_latency(client_options &opt, memcached_st *root) {
149 uint32_t num_of_tests = 100;
150
151 if (auto iter_str = opt.argof("iterations")) {
152 num_of_tests = std::stoul(iter_str);
153 }
154
155 std::vector<memcached_st> servers{memcached_server_count(root)};
156
157 uint32_t i = 0;
158 for (auto &memc : servers) {
159 memcached_clone(&memc, root);
160 memcached_servers_reset(&memc);
161 auto instance = memcached_server_instance_by_position(root, i++);
162 memcached_server_add(&memc, memcached_server_name(instance), memcached_server_port(instance));
163 //pre-connect
164 memcached_version(&memc);
165 }
166
167 latency_test(num_of_tests, servers);
168
169 for (auto &memc : servers) {
170 memcached_free(&memc);
171 }
172
173 return true;
174 }
175
176 static memcached_return_t print_stat(const memcached_instance_st *server,
177 const char *key, size_t key_length,
178 const char *value, size_t value_length, void *context) {
179 auto instance = static_cast<const memcached_instance_st **>(context);
180
181 if (*instance != server) {
182 *instance = server;
183
184 std::cout << "Server: " << memcached_server_name(server)
185 << " (" << memcached_server_port(server) << ")\n";
186 }
187
188 std::cout << "\t";
189 std::cout.write(key, key_length) << ": ";
190 std::cout.write(value, value_length) << "\n";
191
192 return MEMCACHED_SUCCESS;
193 }
194
195 static bool memstat(const client_options &opt, memcached_st &memc, const char *arg) {
196 memcached_instance_st *context = nullptr;
197 auto rc = memcached_stat_execute(&memc, arg, print_stat, &context);
198 if (memcached_success(rc)) {
199 return true;
200 }
201 if (!opt.isset("quiet")) {
202 std::cerr << "Failed to 'STAT " << (arg ? arg : "") << "': ";
203 if (memcached_last_error(&memc)) {
204 std::cerr << memcached_last_error_message(&memc) << "\n";
205 } else {
206 std::cerr << memcached_strerror(&memc, rc) << "\n";
207 }
208 }
209 return false;
210 }
211
212 int main(int argc, char *argv[]) {
213 client_options opt{PROGRAM_NAME, PROGRAM_VERSION, PROGRAM_DESCRIPTION, "[stat ...]"};
214
215 for (const auto &def : opt.defaults) {
216 if (def.opt.val != 'H') {
217 // no need for --hash
218 opt.add(def);
219 }
220 }
221
222 opt.add("args", 'A', required_argument, "Stat args. DEPRECATED: use positional arguments.");
223 opt.add("server-version", 'S', no_argument, "Print server version.");
224 opt.add("analyze", 'a', optional_argument, "Analyze server characteristics (options: default, latency).");
225 opt.add("iterations", 0, required_argument, "Iteration count of GETs sent by the latency test (default: 1000).");
226
227 char **argp = nullptr;
228 if (!opt.parse(argc, argv, &argp)) {
229 exit(EXIT_FAILURE);
230 }
231
232 memcached_st memc;
233 if (!check_memcached(opt, memc)) {
234 exit(EXIT_FAILURE);
235 }
236
237 if (!opt.apply(&memc)) {
238 memcached_free(&memc);
239 exit(EXIT_FAILURE);
240 }
241
242 auto exit_code = EXIT_SUCCESS;
243 if (opt.isset('S')) {
244 if (opt.isset("verbose")) {
245 std::cout << "Server versions:\n";
246 }
247 if (MEMCACHED_SUCCESS != memcached_version(&memc)) {
248 exit_code = EXIT_FAILURE;
249 }
250 memcached_server_fn cb[] = {&print_server_version};
251 memcached_server_cursor(&memc, cb, nullptr, 1);
252 goto done;
253 }
254
255 if (opt.isset("analyze")) {
256 const char *analyze = opt.argof("analyze");
257 if (analyze && strcmp(analyze, "default")) {
258 if (!strcmp(analyze, "latency")) {
259 if (!analyze_latency(opt, &memc)) {
260 exit_code = EXIT_FAILURE;
261 }
262 goto done;
263 }
264
265 if (!opt.isset("quiet")) {
266 std::cerr << "Unknown --analyze mode: '" << analyze << "'.\n";
267 }
268 }
269
270 memcached_return_t rc;
271 auto stat = memcached_stat(&memc, nullptr, &rc);
272 if (!memcached_success(rc)) {
273 exit_code = EXIT_FAILURE;
274 if (!opt.isset("quiet")) {
275 std::cerr << memcached_last_error_message(&memc) << "\n";
276 }
277 } else if (!analyze_stat(opt, &memc, stat)) {
278 exit_code = EXIT_FAILURE;
279 }
280 memcached_stat_free(&memc, stat);
281 goto done;
282 }
283
284 if (!*argp || opt.isset('A')) {
285 if (!memstat(opt, memc, opt.argof('A'))) {
286 exit_code = EXIT_FAILURE;
287 }
288 }
289 for (auto arg = argp; *arg; ++arg) {
290 if (!memstat(opt, memc, *arg)) {
291 exit_code = EXIT_FAILURE;
292 }
293 }
294
295 done:
296 memcached_free(&memc);
297 exit(exit_code);
298 }