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 +--------------------------------------------------------------------+
16 #include "mem_config.h"
18 #define PROGRAM_NAME "memslap"
19 #define PROGRAM_DESCRIPTION "Generate load against a cluster of memcached servers."
20 #define PROGRAM_VERSION "1.1"
22 #define DEFAULT_INITIAL_LOAD 10000ul
23 #define DEFAULT_EXECUTE_NUMBER 10000ul
24 #define DEFAULT_CONCURRENCY 1ul
26 #include "common/options.hpp"
27 #include "common/checks.hpp"
28 #include "common/time.hpp"
29 #include "common/random.hpp"
35 static std::atomic_bool wakeup
;
37 static memcached_return_t
counter(const memcached_st
*, memcached_result_st
*, void *ctx
) {
38 auto c
= static_cast<size_t *>(ctx
);
40 return MEMCACHED_SUCCESS
;
45 std::vector
<char *> chr
;
46 std::vector
<size_t> len
;
47 explicit data(size_t num
)
59 explicit keyval_st(size_t num_
)
65 for (auto i
= 0u; i
< num
; ++i
) {
66 gen(key
.chr
[i
], key
.len
[i
], val
.chr
[i
], val
.len
[i
]);
71 for (auto i
= 0u; i
< num
; ++i
) {
78 void gen_key(char *&key_chr
, size_t &key_len
) {
80 key_chr
= new char[key_len
+ 1];
81 rnd
.fill(key_chr
, key_len
);
84 void gen_val(const char *key_chr
, const size_t key_len
, char *&val_chr
, size_t &val_len
) {
85 val_len
= rnd(50, 5000);
86 val_chr
= new char[val_len
];
88 for (auto len
= 0u; len
< val_len
; len
+= key_len
) {
89 auto val_pos
= val_chr
+ len
;
90 auto rem_len
= len
+ key_len
> val_len
? val_len
% key_len
: key_len
;
91 memcpy(val_pos
, key_chr
, rem_len
);
94 void gen(char *&key_chr
, size_t &key_len
, char *&val_chr
, size_t &val_len
) {
95 gen_key(key_chr
, key_len
);
96 gen_val(key_chr
, key_len
, val_chr
, val_len
);
100 static size_t execute_get(const client_options
&opt
, memcached_st
&memc
, const keyval_st
&kv
) {
101 size_t retrieved
= 0;
104 for (auto i
= 0u; i
< kv
.num
; ++i
) {
105 memcached_return_t rc
;
106 auto r
= rnd(0, kv
.num
);
107 free(memcached_get(&memc
, kv
.key
.chr
[r
], kv
.key
.len
[r
], nullptr, nullptr, &rc
));
109 if (check_return(opt
, memc
, kv
.key
.chr
[r
], rc
)) {
117 static size_t execute_mget(const client_options
&opt
, memcached_st
&memc
, const keyval_st
&kv
) {
118 size_t retrieved
= 0;
119 memcached_execute_fn cb
[] = {&counter
};
121 auto rc
= memcached_mget_execute(&memc
, kv
.key
.chr
.data(), kv
.key
.len
.data(), kv
.num
, cb
, &retrieved
, 1);
123 while (rc
!= MEMCACHED_END
&& memcached_success(rc
)) {
124 rc
= memcached_fetch_execute(&memc
, cb
, &retrieved
, 1);
127 if (memcached_fatal(rc
)) {
128 if (!opt
.isset("quiet")) {
129 std::cerr
<< "Failed mget: " << memcached_strerror(&memc
, rc
) << ": "
130 << memcached_last_error_message(&memc
);
136 static size_t execute_set(const client_options
&opt
, memcached_st
&memc
, const keyval_st
&kv
) {
137 for (auto i
= 0u; i
< kv
.num
; ++i
) {
138 auto rc
= memcached_set(&memc
, kv
.key
.chr
[i
], kv
.key
.len
[i
], kv
.val
.chr
[i
], kv
.val
.len
[i
], 0, 0);
140 if (!check_return(opt
, memc
, kv
.key
.chr
[i
], rc
)) {
141 memcached_quit(&memc
);
149 class thread_context
{
151 thread_context(const client_options
&opt_
, const memcached_st
&memc_
, const keyval_st
&kv_
)
157 , thread([this]{ execute(); })
161 memcached_free(&memc
);
170 const client_options
&opt
;
173 const memcached_st
&root
;
178 memcached_clone(&memc
, &root
);
180 while (!wakeup
.load(std::memory_order_acquire
)) {
181 std::this_thread::yield();
184 if (!strcmp("set", opt
.argof("test"))) {
185 count
= execute_set(opt
, memc
, kv
);
186 } else if (!strcmp("mget", opt
.argof("test"))) {
187 count
= execute_mget(opt
, memc
, kv
);
189 if (strcmp("get", opt
.argof("test"))) {
190 if (!opt
.isset("quiet")) {
191 std::cerr
<< "Unknown --test: '" << opt
.argof("test") << "'.\n";
194 count
= execute_get(opt
, memc
, kv
);
199 using opt_apply
= std::function
<bool(const client_options
&, const client_options::extended_option
&ext
, memcached_st
*)>;
201 static opt_apply
wrap_stoul(unsigned long &ul
) {
202 return [&ul
](const client_options
&, const client_options::extended_option
&ext
, memcached_st
*) {
203 if (ext
.arg
&& *ext
.arg
) {
204 auto c
= std::stoul(ext
.arg
);
213 int main(int argc
, char *argv
[]) {
214 client_options opt
{PROGRAM_NAME
, PROGRAM_VERSION
, PROGRAM_DESCRIPTION
};
215 auto concurrency
= DEFAULT_CONCURRENCY
;
216 auto load_count
= DEFAULT_INITIAL_LOAD
;
217 auto test_count
= DEFAULT_EXECUTE_NUMBER
;
219 for (const auto &def
: opt
.defaults
) {
223 opt
.add("noreply", 'R', no_argument
, "Enable the NOREPLY behavior for storage commands.")
224 .apply
= [](const client_options
&opt_
, const client_options::extended_option
&ext
, memcached_st
*memc
) {
225 if (MEMCACHED_SUCCESS
!= memcached_behavior_set(memc
, MEMCACHED_BEHAVIOR_NOREPLY
, ext
.set
)) {
226 if(!opt_
.isset("quiet")) {
227 std::cerr
<< memcached_last_error_message(memc
);
233 opt
.add("udp", 'U', no_argument
, "Use UDP.")
234 .apply
= [](const client_options
&opt_
, const client_options::extended_option
&ext
, memcached_st
*memc
) {
235 if (MEMCACHED_SUCCESS
!= memcached_behavior_set(memc
, MEMCACHED_BEHAVIOR_USE_UDP
, ext
.set
)) {
236 if (!opt_
.isset("quiet")) {
237 std::cerr
<< memcached_last_error_message(memc
) << "\n";
243 opt
.add("flush", 'F', no_argument
, "Flush all servers prior test.");
244 opt
.add("test", 't', required_argument
, "Test to perform (options: get,mget,set; default: get).");
245 opt
.add("concurrency", 'c', required_argument
, "Concurrency (number of threads to start; default: 1).")
246 .apply
= wrap_stoul(concurrency
);
247 opt
.add("execute-number", 'e', required_argument
, "Number of times to execute the tests (default: 10000).")
248 .apply
= wrap_stoul(test_count
);
249 opt
.add("initial-load", 'l', required_argument
, "Number of keys to load before executing tests (default: 10000)."
250 "\n\t\tDEPRECATED: --execute-number takes precedence.")
251 .apply
= wrap_stoul(load_count
);
254 opt
.set("test", true, set
);
256 if (!opt
.parse(argc
, argv
)) {
261 if (!check_memcached(opt
, memc
)) {
265 if (!opt
.apply(&memc
)) {
266 memcached_free(&memc
);
270 auto total_start
= time_clock::now();
271 std::cout
<< std::fixed
<< std::setprecision(3);
273 auto align
= [](std::ostream
&io
) -> std::ostream
&{
274 return io
<< std::right
<< std::setw(8);
277 if (opt
.isset("flush")) {
278 if (opt
.isset("verbose")) {
279 std::cout
<< "- Flushing servers ...\n";
281 auto flush_start
= time_clock::now();
282 auto rc
= memcached_flush(&memc
, 0);
283 auto flush_elapsed
= time_clock::now() - flush_start
;
284 if (!memcached_success(rc
)) {
285 if (!opt
.isset("quiet")) {
286 std::cerr
<< "Failed to FLUSH: " << memcached_last_error_message(&memc
) << "\n";
288 memcached_free(&memc
);
291 if (!opt
.isset("quiet")) {
292 std::cout
<< "Time to flush " << align
293 << memcached_server_count(&memc
)
295 << align
<< time_format(flush_elapsed
).count()
300 if (opt
.isset("verbose")) {
301 std::cout
<< "- Generating random test data ...\n";
303 auto keyval_start
= time_clock::now();
304 keyval_st kv
{test_count
};
305 auto keyval_elapsed
= time_clock::now() - keyval_start
;
307 if (!opt
.isset("quiet")) {
308 std::cout
<< "Time to generate "
309 << align
<< test_count
311 << align
<< time_format(keyval_elapsed
).count()
315 if (strcmp(opt
.argof("test"), "set")) {
316 if (opt
.isset("verbose")) {
317 std::cout
<< "- Feeding initial load to servers ...\n";
319 auto feed_start
= time_clock::now();
320 auto count
= execute_set(opt
, memc
, kv
);
321 check_return(opt
, memc
, memcached_flush_buffers(&memc
));
322 auto feed_elapsed
= time_clock::now() - feed_start
;
324 if (!opt
.isset("quiet")) {
325 std::cout
<< "Time to set "
328 << align
<< time_format(feed_elapsed
).count()
333 if (opt
.isset("verbose")) {
334 std::cout
<< "- Starting " << concurrency
<< " threads ...\n";
336 auto thread_start
= time_clock::now();
337 std::vector
<thread_context
*> threads
{};
338 threads
.reserve(concurrency
);
339 for (auto i
= 0ul; i
< concurrency
; ++i
) {
340 threads
.push_back(new thread_context(opt
, memc
, kv
));
342 auto thread_elapsed
= time_clock::now() - thread_start
;
343 if (!opt
.isset("quiet")) {
344 std::cout
<< "Time to start "
345 << align
<< concurrency
347 << time_format(thread_elapsed
).count()
350 if (opt
.isset("verbose")) {
351 std::cout
<< "- Starting test: " << test_count
352 << " x " << opt
.argof("test")
353 << " x " << concurrency
357 auto test_start
= time_clock::now();
358 wakeup
.store(true, std::memory_order_release
);
359 for (auto &thread
: threads
) {
360 count
+= thread
->complete();
363 auto test_elapsed
= time_clock::now() - test_start
;
365 if (!opt
.isset("quiet")) {
366 std::cout
<< "--------------------------------------------------------------------\n"
367 << "Time to " << std::left
<< std::setw(4)
368 << opt
.argof("test") << " "
372 << concurrency
<< " threads: "
373 << align
<< time_format(test_elapsed
).count()
376 std::cout
<< "--------------------------------------------------------------------\n"
378 << align
<< std::setw(12)
379 << time_format(time_clock::now() - total_start
).count()