1 /* -*- Mode: C++; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 /*
3  *     Copyright 2010 Couchbase, Inc
4  *
5  *   Licensed under the Apache License, Version 2.0 (the "License");
6  *   you may not use this file except in compliance with the License.
7  *   You may obtain a copy of the License at
8  *
9  *       http://www.apache.org/licenses/LICENSE-2.0
10  *
11  *   Unless required by applicable law or agreed to in writing, software
12  *   distributed under the License is distributed on an "AS IS" BASIS,
13  *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  *   See the License for the specific language governing permissions and
15  *   limitations under the License.
16  */
17 #include "config.h"
18 
19 #include <memcached/engine.h>
20 #include <memcached/engine_testapp.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 
25 #include <algorithm>
26 #include <cstdlib>
27 #include <iostream>
28 #include <map>
29 #include <sstream>
30 #include <string>
31 #include <vector>
32 
33 #ifdef linux
34 /* /usr/include/netinet/in.h defines macros from ntohs() to _bswap_nn to
35  * optimize the conversion functions, but the prototypes generate warnings
36  * from gcc. The conversion methods isn't the bottleneck for my app, so
37  * just remove the warnings by undef'ing the optimization ..
38  */
39 #undef ntohs
40 #undef ntohl
41 #undef htons
42 #undef htonl
43 #endif
44 
45 bool abort_msg(const char *expr, const char *msg, int line);
46 
47 #define check(expr, msg) \
48     static_cast<void>((expr) ? 0 : abort_msg(#expr, msg, __LINE__))
49 
50 protocol_binary_response_status last_status(static_cast<protocol_binary_response_status>(0));
51 char *last_key = NULL;
52 char *last_body = NULL;
53 std::map<std::string, std::string> vals;
54 
55 struct test_harness testHarness;
56 
abort_msg(const char *expr, const char *msg, int line)57 bool abort_msg(const char *expr, const char *msg, int line) {
58     fprintf(stderr, "%s:%d Test failed: `%s' (%s)\n",
59             __FILE__, line, msg, expr);
60     abort();
61     // UNREACHABLE
62     return false;
63 }
64 
65 extern "C" {
rmdb(void)66     static void rmdb(void) {
67 #ifdef WIN32
68         _unlink("/tmp/test");
69 #else
70         unlink("/tmp/test");
71 #endif
72     }
73 
teardown(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1)74     static bool teardown(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1) {
75         (void)h; (void)h1;
76         atexit(rmdb);
77         vals.clear();
78         return true;
79     }
80 }
81 
decayingSleep(useconds_t *sleepTime)82 static inline void decayingSleep(useconds_t *sleepTime) {
83     static const useconds_t maxSleepTime = 500000;
84     usleep(*sleepTime);
85     *sleepTime = std::min(*sleepTime << 1, maxSleepTime);
86 }
87 
storeCasVb11(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1, const void *cookie, ENGINE_STORE_OPERATION op, const char *key, const char *value, size_t vlen, uint32_t flags, item **outitem, uint64_t casIn, uint16_t vb)88 static ENGINE_ERROR_CODE storeCasVb11(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1,
89                                       const void *cookie,
90                                       ENGINE_STORE_OPERATION op,
91                                       const char *key,
92                                       const char *value, size_t vlen,
93                                       uint32_t flags,
94                                       item **outitem, uint64_t casIn,
95                                       uint16_t vb) {
96     item *it = NULL;
97     uint64_t cas = 0;
98 
99     ENGINE_ERROR_CODE rv = h1->allocate(h, cookie, &it,
100                                         key, strlen(key),
101                                         vlen, flags, 3600,
102                                         PROTOCOL_BINARY_RAW_BYTES);
103     check(rv == ENGINE_SUCCESS, "Allocation failed.");
104 
105     item_info info;
106     info.nvalue = 1;
107     if (!h1->get_item_info(h, cookie, it, &info)) {
108         abort();
109     }
110 
111     memcpy(info.value[0].iov_base, value, vlen);
112     h1->item_set_cas(h, cookie, it, casIn);
113 
114     rv = h1->store(h, cookie, it, &cas, op, vb);
115 
116     if (outitem) {
117         *outitem = it;
118     }
119 
120     return rv;
121 }
122 
123 extern "C" {
add_stats(const char *key, const uint16_t klen, const char *val, const uint32_t vlen, const void *cookie)124     static void add_stats(const char *key, const uint16_t klen,
125                           const char *val, const uint32_t vlen,
126                           const void *cookie) {
127         (void)cookie;
128         std::string k(key, klen);
129         std::string v(val, vlen);
130         vals[k] = v;
131     }
132 }
133 
get_int_stat(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1, const char *statname, const char *statkey = NULL)134 static int get_int_stat(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1,
135                         const char *statname, const char *statkey = NULL) {
136     vals.clear();
137     check(h1->get_stats(h, NULL, statkey, statkey == NULL ? 0 : strlen(statkey),
138                         add_stats) == ENGINE_SUCCESS,
139           "Failed to get stats.");
140     std::string s = vals[statname];
141     return atoi(s.c_str());
142 }
143 
verify_curr_items(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1, int exp, const char *msg)144 static void verify_curr_items(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1,
145                               int exp, const char *msg) {
146     int curr_items = get_int_stat(h, h1, "curr_items");
147     if (curr_items != exp) {
148         std::cerr << "Expected "<< exp << " curr_items after " << msg
149                   << ", got " << curr_items << std::endl;
150         abort();
151     }
152 }
153 
wait_for_flusher_to_settle(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1)154 static void wait_for_flusher_to_settle(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1) {
155     useconds_t sleepTime = 128;
156     while (get_int_stat(h, h1, "ep_queue_size") > 0) {
157         decayingSleep(&sleepTime);
158     }
159 }
160 
env_int(const char *k, size_t rv)161 static size_t env_int(const char *k, size_t rv) {
162     char *x = getenv(k);
163     if (x) {
164         rv = static_cast<size_t>(atoi(x));
165     }
166     return rv;
167 }
168 
169 extern "C" {
test_persistence(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1)170 static test_result test_persistence(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1) {
171     size_t total = env_int("TEST_TOTAL_KEYS", 100000);
172     size_t size = env_int("TEST_VAL_SIZE", 20);
173 
174     char key[24];
175     char *data;
176     data = static_cast<char *>(malloc(sizeof(char) * size));
177     cb_assert(data);
178 
179     for (size_t i = 0; i < (sizeof(char) * size); ++i) {
180         data[i] = 0xff & rand();
181     }
182 
183     for (size_t i = 0; i < total; ++i) {
184         item *it = NULL;
185         snprintf(key, sizeof(key), "k%d", static_cast<int>(i));
186 
187         check(storeCasVb11(h, h1, NULL, OPERATION_SET, key, data,
188                            size, 9713, &it, 0, 0) == ENGINE_SUCCESS,
189                   "store failure");
190     }
191     free(data);
192     wait_for_flusher_to_settle(h, h1);
193 
194     std::cout << total << " at " << size << " - "
195               << get_int_stat(h, h1, "ep_flush_duration_total")
196               << ", " << get_int_stat(h, h1, "ep_commit_time_total") << std::endl;
197 
198         verify_curr_items(h, h1, total, "storing something");
199 
200     return SUCCESS;
201 }
202 }
203 
204 extern "C" MEMCACHED_PUBLIC_API
setup_suite(struct test_harness *th)205 bool setup_suite(struct test_harness *th) {
206     testHarness = *th;
207     return true;
208 }
209 
210 extern "C" MEMCACHED_PUBLIC_API
get_tests(void)211 engine_test_t* get_tests(void) {
212 
213     static engine_test_t tests[]  = {
214         {"test persistence", test_persistence, NULL, teardown, NULL,
215          NULL, NULL},
216         {NULL, NULL, NULL, NULL, NULL, NULL, NULL}
217     };
218     return tests;
219 }
220