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