66a46cefe3d2714856e4b8cbbe4a114a851c62ae
[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
97 std::cout << "Network Latency Test:\n\n" << std::showpoint << std::fixed << std::setprecision(3);
98
99 for (auto &memc : servers) {
100 memcached_return_t rc;
101
102 auto start = time_clock::now();
103 for (auto i = 0u; i < iterations; ++i) {
104 free(memcached_get(&memc, test_key, test_key_len, nullptr, nullptr, &rc));
105 if (memcached_fatal(rc)) {
106 break;
107 }
108 }
109 auto elapsed = time_clock::now() - start;
110
111 auto inst = memcached_server_instance_by_position(&memc, 0);
112 std::cout << "\t " << memcached_server_name(inst)
113 << " (" << memcached_server_port(inst) << ") ";
114
115 if (memcached_fatal(rc)) {
116 std::cout << " => failed to reach the server\n";
117 } else {
118 std::cout << " => "
119 << time_format(elapsed/iterations).count() << " seconds ("
120 << time_format_ms(elapsed/iterations).count() << "ms)\n";
121 if (slowest_time == time_point::duration::zero() || slowest_time < elapsed) {
122 slowest_time = elapsed;
123 slowest_server = inst;
124 }
125 }
126 }
127
128 if (servers.size() > 1 && slowest_server) {
129 std::cout << "\n---\n\nSlowest Server: "
130 << memcached_server_name(slowest_server) << "("
131 << memcached_server_port(slowest_server) << ")"
132 << " => "
133 << time_format(slowest_time/iterations).count() << " seconds ("
134 << time_format_ms(slowest_time/iterations).count() << "ms)\n";
135 }
136 }
137
138 static bool analyze_latency(client_options &opt, memcached_st *root) {
139 uint32_t num_of_tests = 32;
140
141 std::vector<memcached_st> servers{memcached_server_count(root)};
142
143 uint32_t i = 0;
144 for (auto &memc : servers) {
145 if (!check_memcached(opt, memc)) {
146 return false;
147 }
148 auto instance = memcached_server_instance_by_position(root, i++);
149 memcached_server_add(&memc, memcached_server_name(instance), memcached_server_port(instance));
150 }
151
152 latency_test(num_of_tests, servers);
153
154 for (auto &memc : servers) {
155 memcached_free(&memc);
156 }
157
158 return true;
159 }
160
161 static memcached_return_t print_stat(const memcached_instance_st *server,
162 const char *key, size_t key_length,
163 const char *value, size_t value_length, void *context) {
164 auto instance = static_cast<const memcached_instance_st **>(context);
165
166 if (*instance != server) {
167 *instance = server;
168
169 std::cout << "Server: " << memcached_server_name(server)
170 << " (" << memcached_server_port(server) << ")\n";
171 }
172
173 std::cout << "\t";
174 std::cout.write(key, key_length) << ": ";
175 std::cout.write(value, value_length) << "\n";
176
177 return MEMCACHED_SUCCESS;
178 }
179
180 static bool memstat(const client_options &opt, memcached_st &memc, const char *arg) {
181 memcached_instance_st *context = nullptr;
182 auto rc = memcached_stat_execute(&memc, arg, print_stat, &context);
183 if (memcached_success(rc)) {
184 return true;
185 }
186 if (!opt.isset("quiet")) {
187 std::cerr << "Failed to 'STAT " << (arg ? arg : "") << "': ";
188 if (memcached_last_error(&memc)) {
189 std::cerr << memcached_last_error_message(&memc) << "\n";
190 } else {
191 std::cerr << memcached_strerror(&memc, rc) << "\n";
192 }
193 }
194 return false;
195 }
196
197 int main(int argc, char *argv[]) {
198 client_options opt{PROGRAM_NAME, PROGRAM_VERSION, PROGRAM_DESCRIPTION, "[stat ...]"};
199
200 for (const auto &def : opt.defaults) {
201 if (def.opt.val != 'H') {
202 // no need for --hash
203 opt.add(def);
204 }
205 }
206
207 opt.add("args", 'A', required_argument, "Stat args. DEPRECATED: use positional arguments.");
208 opt.add("server-version", 'S', no_argument, "Print server version.");
209 opt.add("analyze", 'a', optional_argument, "Analyze server characteristics (options: default, latency).");
210
211 char **argp = nullptr;
212 if (!opt.parse(argc, argv, &argp)) {
213 exit(EXIT_FAILURE);
214 }
215
216 memcached_st memc;
217 if (!check_memcached(opt, memc)) {
218 exit(EXIT_FAILURE);
219 }
220
221 if (!opt.apply(&memc)) {
222 memcached_free(&memc);
223 exit(EXIT_FAILURE);
224 }
225
226 auto exit_code = EXIT_SUCCESS;
227 if (opt.isset('S')) {
228 if (opt.isset("verbose")) {
229 std::cout << "Server versions:\n";
230 }
231 if (MEMCACHED_SUCCESS != memcached_version(&memc)) {
232 exit_code = EXIT_FAILURE;
233 }
234 memcached_server_fn cb[] = {&print_server_version};
235 memcached_server_cursor(&memc, cb, nullptr, 1);
236 goto done;
237 }
238
239 if (opt.isset("analyze")) {
240 const char *analyze = opt.argof("analyze");
241 if (analyze && strcmp(analyze, "default")) {
242 if (!strcmp(analyze, "latency")) {
243 if (!analyze_latency(opt, &memc)) {
244 exit_code = EXIT_FAILURE;
245 }
246 goto done;
247 }
248
249 if (!opt.isset("quiet")) {
250 std::cerr << "Unknown --analyze mode: '" << analyze << "'.\n";
251 }
252 }
253
254 memcached_return_t rc;
255 auto stat = memcached_stat(&memc, nullptr, &rc);
256 if (!memcached_success(rc)) {
257 exit_code = EXIT_FAILURE;
258 if (!opt.isset("quiet")) {
259 std::cerr << memcached_last_error_message(&memc) << "\n";
260 }
261 } else if (!analyze_stat(opt, &memc, stat)) {
262 exit_code = EXIT_FAILURE;
263 }
264 memcached_stat_free(&memc, stat);
265 goto done;
266 }
267
268 if (!*argp || opt.isset('A')) {
269 if (!memstat(opt, memc, opt.argof('A'))) {
270 exit_code = EXIT_FAILURE;
271 }
272 }
273 for (auto arg = argp; *arg; ++arg) {
274 if (!memstat(opt, memc, *arg)) {
275 exit_code = EXIT_FAILURE;
276 }
277 }
278
279 done:
280 memcached_free(&memc);
281 exit(exit_code);
282 }