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
45bool 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
50protocol_binary_response_status last_status(static_cast<protocol_binary_response_status>(0));
51char *last_key = NULL;
52char *last_body = NULL;
53std::map<std::string, std::string> vals;
54
55struct test_harness testHarness;
56
57bool 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
65extern "C" {
66    static void rmdb(void) {
67#ifdef WIN32
68        _unlink("/tmp/test");
69#else
70        unlink("/tmp/test");
71#endif
72    }
73
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
82static 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
88static 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
123extern "C" {
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
134static 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
144static 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
154static 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
161static 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
169extern "C" {
170static 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
204extern "C" MEMCACHED_PUBLIC_API
205bool setup_suite(struct test_harness *th) {
206    testHarness = *th;
207    return true;
208}
209
210extern "C" MEMCACHED_PUBLIC_API
211engine_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