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
35bool 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
40protocol_binary_response_status last_status(static_cast<protocol_binary_response_status>(0));
41char *last_key = NULL;
42char *last_body = NULL;
43std::map<std::string, std::string> vals;
44
45struct test_harness testHarness;
46
47bool 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
55extern "C" {
56    static void rmdb(void) {
57#ifdef WIN32
58        _unlink("./test");
59#else
60        unlink("./test");
61#endif
62    }
63
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
72static 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
78static 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
116extern "C" {
117static 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
128static 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
142static 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
152static 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
159static 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
167extern "C" {
168static 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
202extern "C" MEMCACHED_PUBLIC_API
203bool setup_suite(struct test_harness *th) {
204    testHarness = *th;
205    return true;
206}
207
208extern "C" MEMCACHED_PUBLIC_API
209engine_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