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