2 +--------------------------------------------------------------------+
3 | libmemcached-awesome - 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-2021 Michael Wallner https://awesome.co/ |
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
};
120 memcached_return_t rc
;
122 if (memcached_is_binary(&memc
)) {
123 rc
= memcached_mget_execute(&memc
, kv
.key
.chr
.data(), kv
.key
.len
.data(), kv
.num
, cb
,
126 while (rc
!= MEMCACHED_END
&& memcached_success(rc
)) {
127 rc
= memcached_fetch_execute(&memc
, cb
, &retrieved
, 1);
130 memcached_result_st res
;
131 memcached_result_create(&memc
, &res
);
133 rc
= memcached_mget(&memc
, kv
.key
.chr
.data(), kv
.key
.len
.data(), kv
.num
);
135 while (rc
!= MEMCACHED_END
&& memcached_success(rc
)) {
136 if (memcached_fetch_result(&memc
, &res
, &rc
)) {
140 memcached_result_free(&res
);
142 if (memcached_fatal(rc
)) {
143 if (!opt
.isset("quiet")) {
144 std::cerr
<< "Failed mget: " << memcached_strerror(&memc
, rc
) << ": "
145 << memcached_last_error_message(&memc
);
151 static size_t execute_set(const client_options
&opt
, memcached_st
&memc
, const keyval_st
&kv
) {
152 for (auto i
= 0u; i
< kv
.num
; ++i
) {
153 auto rc
= memcached_set(&memc
, kv
.key
.chr
[i
], kv
.key
.len
[i
], kv
.val
.chr
[i
], kv
.val
.len
[i
], 0, 0);
155 if (!check_return(opt
, memc
, kv
.key
.chr
[i
], rc
)) {
156 memcached_quit(&memc
);
164 class thread_context
{
166 thread_context(const client_options
&opt_
, const memcached_st
&memc_
, const keyval_st
&kv_
)
172 , thread([this]{ execute(); })
176 memcached_free(&memc
);
185 const client_options
&opt
;
188 const memcached_st
&root
;
193 memcached_clone(&memc
, &root
);
195 while (!wakeup
.load(std::memory_order_acquire
)) {
196 std::this_thread::yield();
199 if (!strcmp("set", opt
.argof("test"))) {
200 count
= execute_set(opt
, memc
, kv
);
201 } else if (!strcmp("mget", opt
.argof("test"))) {
202 count
= execute_mget(opt
, memc
, kv
);
204 if (strcmp("get", opt
.argof("test"))) {
205 if (!opt
.isset("quiet")) {
206 std::cerr
<< "Unknown --test: '" << opt
.argof("test") << "'.\n";
209 count
= execute_get(opt
, memc
, kv
);
214 using opt_apply
= std::function
<bool(const client_options
&, const client_options::extended_option
&ext
, memcached_st
*)>;
216 static opt_apply
wrap_stoul(unsigned long &ul
) {
217 return [&ul
](const client_options
&, const client_options::extended_option
&ext
, memcached_st
*) {
218 if (ext
.arg
&& *ext
.arg
) {
219 auto c
= std::stoul(ext
.arg
);
228 static std::ostream
&align(std::ostream
&io
) {
229 return io
<< std::right
<< std::setw(8);
232 int main(int argc
, char *argv
[]) {
233 client_options opt
{PROGRAM_NAME
, PROGRAM_VERSION
, PROGRAM_DESCRIPTION
};
234 auto concurrency
= DEFAULT_CONCURRENCY
;
235 auto load_count
= DEFAULT_INITIAL_LOAD
;
236 auto test_count
= DEFAULT_EXECUTE_NUMBER
;
238 for (const auto &def
: opt
.defaults
) {
242 opt
.add("noreply", 'R', no_argument
, "Enable the NOREPLY behavior for storage commands.")
243 .apply
= [](const client_options
&opt_
, const client_options::extended_option
&ext
, memcached_st
*memc
) {
244 if (MEMCACHED_SUCCESS
!= memcached_behavior_set(memc
, MEMCACHED_BEHAVIOR_NOREPLY
, ext
.set
)) {
245 if(!opt_
.isset("quiet")) {
246 std::cerr
<< memcached_last_error_message(memc
);
252 opt
.add("udp", 'U', no_argument
, "Use UDP.")
253 .apply
= [](const client_options
&opt_
, const client_options::extended_option
&ext
, memcached_st
*memc
) {
254 if (MEMCACHED_SUCCESS
!= memcached_behavior_set(memc
, MEMCACHED_BEHAVIOR_USE_UDP
, ext
.set
)) {
255 if (!opt_
.isset("quiet")) {
256 std::cerr
<< memcached_last_error_message(memc
) << "\n";
262 opt
.add("flush", 'F', no_argument
, "Flush all servers prior test.");
263 opt
.add("test", 't', required_argument
, "Test to perform (options: get,mget,set; default: get).");
264 opt
.add("concurrency", 'c', required_argument
, "Concurrency (number of threads to start; default: 1).")
265 .apply
= wrap_stoul(concurrency
);
266 opt
.add("execute-number", 'e', required_argument
, "Number of times to execute the tests (default: 10000).")
267 .apply
= wrap_stoul(test_count
);
268 opt
.add("initial-load", 'l', required_argument
, "Number of keys to load before executing tests (default: 10000)."
269 "\n\t\tDEPRECATED: --execute-number takes precedence.")
270 .apply
= wrap_stoul(load_count
);
273 opt
.set("test", true, set
);
275 if (!opt
.parse(argc
, argv
)) {
280 if (!check_memcached(opt
, memc
)) {
284 if (!opt
.apply(&memc
)) {
285 memcached_free(&memc
);
289 auto total_start
= time_clock::now();
290 std::cout
<< std::fixed
<< std::setprecision(3);
292 if (opt
.isset("flush")) {
293 if (opt
.isset("verbose")) {
294 std::cout
<< "- Flushing servers ...\n";
296 auto flush_start
= time_clock::now();
297 auto rc
= memcached_flush(&memc
, 0);
298 auto flush_elapsed
= time_clock::now() - flush_start
;
299 if (!memcached_success(rc
)) {
300 if (!opt
.isset("quiet")) {
301 std::cerr
<< "Failed to FLUSH: " << memcached_last_error_message(&memc
) << "\n";
303 memcached_free(&memc
);
306 if (!opt
.isset("quiet")) {
307 std::cout
<< "Time to flush " << align
308 << memcached_server_count(&memc
)
310 << align
<< time_format(flush_elapsed
).count()
315 if (opt
.isset("verbose")) {
316 std::cout
<< "- Generating random test data ...\n";
318 auto keyval_start
= time_clock::now();
319 keyval_st kv
{test_count
};
320 auto keyval_elapsed
= time_clock::now() - keyval_start
;
322 if (!opt
.isset("quiet")) {
323 std::cout
<< "Time to generate "
324 << align
<< test_count
326 << align
<< time_format(keyval_elapsed
).count()
330 if (strcmp(opt
.argof("test"), "set")) {
331 if (opt
.isset("verbose")) {
332 std::cout
<< "- Feeding initial load to servers ...\n";
334 auto feed_start
= time_clock::now();
335 auto count
= execute_set(opt
, memc
, kv
);
336 check_return(opt
, memc
, memcached_flush_buffers(&memc
));
337 auto feed_elapsed
= time_clock::now() - feed_start
;
339 if (!opt
.isset("quiet")) {
340 std::cout
<< "Time to set "
343 << align
<< time_format(feed_elapsed
).count()
348 if (opt
.isset("verbose")) {
349 std::cout
<< "- Starting " << concurrency
<< " threads ...\n";
351 auto thread_start
= time_clock::now();
352 std::vector
<thread_context
*> threads
{};
353 threads
.reserve(concurrency
);
354 for (auto i
= 0ul; i
< concurrency
; ++i
) {
355 threads
.push_back(new thread_context(opt
, memc
, kv
));
357 auto thread_elapsed
= time_clock::now() - thread_start
;
358 if (!opt
.isset("quiet")) {
359 std::cout
<< "Time to start "
360 << align
<< concurrency
362 << time_format(thread_elapsed
).count()
365 if (opt
.isset("verbose")) {
366 std::cout
<< "- Starting test: " << test_count
367 << " x " << opt
.argof("test")
368 << " x " << concurrency
372 auto test_start
= time_clock::now();
373 wakeup
.store(true, std::memory_order_release
);
374 for (auto &thread
: threads
) {
375 count
+= thread
->complete();
378 auto test_elapsed
= time_clock::now() - test_start
;
380 if (!opt
.isset("quiet")) {
381 std::cout
<< "--------------------------------------------------------------------\n"
382 << "Time to " << std::left
<< std::setw(4)
383 << opt
.argof("test") << " "
387 << concurrency
<< " threads: "
388 << align
<< time_format(test_elapsed
).count()
391 std::cout
<< "--------------------------------------------------------------------\n"
393 << align
<< std::setw(12)
394 << time_format(time_clock::now() - total_start
).count()