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 <platform/cb_malloc.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <unistd.h>
26 
27 #include <algorithm>
28 #include <cstdlib>
29 #include <iostream>
30 #include <map>
31 #include <sstream>
32 #include <string>
33 #include <vector>
34 
35 bool abort_msg(const char *expr, const char *msg, int line);
36 
37 #define check(expr, msg) \
38     static_cast<void>((expr) ? 0 : abort_msg(#expr, msg, __LINE__))
39 
40 protocol_binary_response_status last_status(static_cast<protocol_binary_response_status>(0));
41 char *last_key = NULL;
42 char *last_body = NULL;
43 std::map<std::string, std::string> vals;
44 
45 struct test_harness testHarness;
46 
abort_msg(const char *expr, const char *msg, int line)47 bool abort_msg(const char *expr, const char *msg, int line) {
48     fprintf(stderr, "%s:%d Test failed: `%s' (%s)\n",
49             __FILE__, line, msg, expr);
50     abort();
51     // UNREACHABLE
52     return false;
53 }
54 
55 extern "C" {
rmdb(void)56     static void rmdb(void) {
57 #ifdef WIN32
58         _unlink("./test");
59 #else
60         unlink("./test");
61 #endif
62     }
63 
teardown(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1)64     static bool teardown(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1) {
65         (void)h; (void)h1;
66         atexit(rmdb);
67         vals.clear();
68         return true;
69     }
70 }
71 
decayingSleep(useconds_t *sleepTime)72 static inline void decayingSleep(useconds_t *sleepTime) {
73     static const useconds_t maxSleepTime = 500000;
74     usleep(*sleepTime);
75     *sleepTime = std::min(*sleepTime << 1, maxSleepTime);
76 }
77 
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)78 static ENGINE_ERROR_CODE storeCasVb11(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1,
79                                       const void *cookie,
80                                       ENGINE_STORE_OPERATION op,
81                                       const char *key,
82                                       const char *value, size_t vlen,
83                                       uint32_t flags,
84                                       item **outitem, uint64_t casIn,
85                                       uint16_t vb) {
86     uint64_t cas = 0;
87 
88     auto ret = h1->allocate(h,
89                             cookie,
90                             DocKey(key, DocNamespace::DefaultCollection),
91                             vlen,
92                             flags,
93                             3600,
94                             PROTOCOL_BINARY_RAW_BYTES,
95                             vb);
96     check(ret.first == cb::engine_errc::success, "Allocation failed.");
97 
98     item_info info;
99     if (!h1->get_item_info(h, ret.second.get(), &info)) {
100         abort();
101     }
102 
103     memcpy(info.value[0].iov_base, value, vlen);
104     h1->item_set_cas(h, ret.second.get(), casIn);
105 
106     auto rv = h1->store(
107             h, cookie, ret.second.get(), cas, op, DocumentState::Alive);
108 
109     if (outitem) {
110         *outitem = ret.second.release();
111     }
112 
113     return rv;
114 }
115 
116 extern "C" {
add_stats(const char* key, const uint16_t klen, const char* val, const uint32_t vlen, gsl::not_null<const void*>)117 static void add_stats(const char* key,
118                       const uint16_t klen,
119                       const char* val,
120                       const uint32_t vlen,
121                       gsl::not_null<const void*>) {
122     std::string k(key, klen);
123     std::string v(val, vlen);
124     vals[k] = v;
125     }
126 }
127 
get_int_stat(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1, const char *statname, const char *statkey = NULL)128 static int get_int_stat(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1,
129                         const char *statname, const char *statkey = NULL) {
130     vals.clear();
131     const auto* cookie = testHarness.create_cookie();
132     check(h1->get_stats(h,
133                         cookie,
134                         {statkey, statkey == NULL ? 0 : strlen(statkey)},
135                         add_stats) == ENGINE_SUCCESS,
136           "Failed to get stats.");
137     testHarness.destroy_cookie(cookie);
138     std::string s = vals[statname];
139     return atoi(s.c_str());
140 }
141 
verify_curr_items(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1, int exp, const char *msg)142 static void verify_curr_items(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1,
143                               int exp, const char *msg) {
144     int curr_items = get_int_stat(h, h1, "curr_items");
145     if (curr_items != exp) {
146         std::cerr << "Expected "<< exp << " curr_items after " << msg
147                   << ", got " << curr_items << std::endl;
148         abort();
149     }
150 }
151 
wait_for_flusher_to_settle(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1)152 static void wait_for_flusher_to_settle(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1) {
153     useconds_t sleepTime = 128;
154     while (get_int_stat(h, h1, "ep_queue_size") > 0) {
155         decayingSleep(&sleepTime);
156     }
157 }
158 
env_int(const char *k, size_t rv)159 static size_t env_int(const char *k, size_t rv) {
160     char *x = getenv(k);
161     if (x) {
162         rv = static_cast<size_t>(atoi(x));
163     }
164     return rv;
165 }
166 
167 extern "C" {
test_persistence(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1)168 static test_result test_persistence(ENGINE_HANDLE *h, ENGINE_HANDLE_V1 *h1) {
169     size_t total = env_int("TEST_TOTAL_KEYS", 100000);
170     size_t size = env_int("TEST_VAL_SIZE", 20);
171 
172     char key[24];
173     char *data;
174     data = static_cast<char *>(cb_malloc(sizeof(char) * size));
175     cb_assert(data);
176 
177     for (size_t i = 0; i < (sizeof(char) * size); ++i) {
178         data[i] = 0xff & rand();
179     }
180 
181     for (size_t i = 0; i < total; ++i) {
182         item *it = NULL;
183         snprintf(key, sizeof(key), "k%d", static_cast<int>(i));
184 
185         check(storeCasVb11(h, h1, NULL, OPERATION_SET, key, data,
186                            size, 9713, &it, 0, 0) == ENGINE_SUCCESS,
187                   "store failure");
188     }
189     cb_free(data);
190     wait_for_flusher_to_settle(h, h1);
191 
192     std::cout << total << " at " << size << " - "
193               << get_int_stat(h, h1, "ep_flush_duration_total")
194               << ", " << get_int_stat(h, h1, "ep_commit_time_total") << std::endl;
195 
196         verify_curr_items(h, h1, total, "storing something");
197 
198     return SUCCESS;
199 }
200 }
201 
202 extern "C" MEMCACHED_PUBLIC_API
setup_suite(struct test_harness *th)203 bool setup_suite(struct test_harness *th) {
204     testHarness = *th;
205     return true;
206 }
207 
208 extern "C" MEMCACHED_PUBLIC_API
get_tests(void)209 engine_test_t* get_tests(void) {
210 
211     static engine_test_t tests[]  = {
212         TEST_CASE("test persistence", test_persistence, NULL, teardown, NULL,
213          NULL, NULL),
214         TEST_CASE(NULL, NULL, NULL, NULL, NULL, NULL, NULL)
215     };
216     return tests;
217 }
218