1 /* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 #include "src/config.h"
3 #include <stdio.h>
4 #include <stdlib.h>
5 #include <string.h>
6 #include <errno.h>
7 #include <assert.h>
8 #include "memcached.h"
9 #include "cproxy.h"
10 #include "work.h"
11 #include "agent.h"
12 #include "log.h"
13 
14 /* Local declarations. */
15 
16 struct ping_test_recipe {
17     char *name;
18     int keysize;
19     int valsize;
20     int iterations;
21 };
22 
23 static void ping_server(char *server_name,
24                         struct ping_test_recipe *recipes,
25                         proxy_behavior *behavior,
26                         conflate_form_result *r);
27 
charlistlen(char **in)28 static int charlistlen(char **in) {
29     int rv = 0;
30     while (in[rv]) {
31         rv++;
32     }
33     return rv;
34 }
35 
starts_with(const char* needle, const char *haystack)36 static bool starts_with(const char* needle, const char *haystack)
37 {
38     return strncmp(needle, haystack, strlen(needle)) == 0;
39 }
40 
load_ping_recipe(char **params, struct ping_test_recipe *out)41 static void load_ping_recipe(char **params,
42                              struct ping_test_recipe *out)
43 {
44     int i;
45 
46     assert(params);
47     assert(out);
48 
49     for (i = 0; params[i]; i++) {
50         int value = 0;
51         char *p = NULL;
52 
53         p = strchr(params[i], '=');
54         assert(p);
55 
56         if (!safe_strtol(p+1, &value)) {
57             moxi_log_write("Failed to parse ``%s'' as number\n", p+1);
58             assert(false);
59         }
60 
61         if (starts_with("ksize=", params[i])) {
62             out->keysize = value;
63         } else if (starts_with("vsize=", params[i])) {
64             out->valsize = value;
65         } else if (starts_with("iterations=", params[i])) {
66             out->iterations = value;
67         } else {
68             if (settings.verbose > 1) {
69                 moxi_log_write("Unknown recipe property:  %s\n", params[i]);
70             }
71         }
72     }
73 }
74 
on_conflate_ping_test(void *userdata, conflate_handle_t *handle, const char *cmd, bool direct, kvpair_t *form, conflate_form_result *r)75 enum conflate_mgmt_cb_result on_conflate_ping_test(void *userdata,
76                                                    conflate_handle_t *handle,
77                                                    const char *cmd,
78                                                    bool direct,
79                                                    kvpair_t *form,
80                                                    conflate_form_result *r)
81 {
82     int j;
83 
84     char detail_key[200];
85 
86     /* Discover test configuration. */
87     char **tests;
88     int nrecipes;
89 
90     /* @todo add calloc */
91     struct ping_test_recipe *recipes;
92     char **servers;
93 
94     (void)userdata;
95     (void)handle;
96     (void)cmd;
97     (void)direct;
98     assert(userdata);
99 
100     /* The form key-multivalues looks roughly like... */
101 
102     /*  servers */
103     /*    svrname1 */
104     /*    svrname2 */
105     /*  svr-svrname1 */
106     /*    host=mc1.foo.net */
107     /*    port=11211 */
108     /*    bucket=buck1 */
109     /*    usr=test1 */
110     /*    pwd=password */
111     /*  svr-svrname2 */
112     /*    host=mc2.foo.net */
113     /*    port=11211 */
114     /*    bucket=buck1 */
115     /*    usr=test1 */
116     /*    pwd=password */
117     /*  tests */
118     /*    test1 */
119     /*    test2 */
120     /*  def-test1 */
121     /*    ksize=16 */
122     /*    vsize=16 */
123     /*    iterations=500 */
124     /*  def-test2 */
125     /*    ksize=64 */
126     /*    vsize=524288 */
127     /*    iterations=50 */
128 
129     if (!form) {
130         return RV_BADARG;
131     }
132 
133     /* Discover test configuration. */
134     tests = get_key_values(form, "tests");
135     nrecipes = charlistlen(tests);
136 
137     recipes = calloc(nrecipes + 1, sizeof(*recipes));
138     /* @todo check return value */
139     if (recipes == NULL) {
140         return RV_ERROR;
141     }
142     for (j = 0; j < nrecipes; j++) {
143         snprintf(detail_key, sizeof(detail_key), "def-%s", tests[j]);
144         recipes[j].name = strdup(detail_key);
145         assert(recipes[j].name);
146         load_ping_recipe(get_key_values(form, detail_key), &recipes[j]);
147     }
148 
149     /* Initialize each server and run the tests */
150     servers = get_key_values(form, "servers");
151     for (j = 0; servers != NULL && servers[j]; j++) {
152         proxy_behavior behavior;
153         char **props;
154         int k;
155 
156         snprintf(detail_key, sizeof(detail_key),
157                  "svr-%s", servers[j]);
158 
159         if (settings.verbose > 1) {
160             moxi_log_write("ping_test %s\n", detail_key);
161         }
162 
163         memset(&behavior, 0, sizeof(behavior));
164 
165         props = get_key_values(form, detail_key);
166         for (k = 0; props && props[k]; k++) {
167             cproxy_parse_behavior_key_val_str(props[k], &behavior);
168         }
169 
170         ping_server(servers[j], recipes, &behavior, r);
171     }
172 
173     /* The recipe memory allocations */
174     for (j = 0; j < nrecipes; j++) {
175         free(recipes[j].name);
176     }
177 
178     free(recipes);
179     return RV_OK;
180 }
181 
perform_ping_test(struct ping_test_recipe recipe, memcached_st *mst, struct moxi_stats *out, int *failures)182 static void perform_ping_test(struct ping_test_recipe recipe,
183                               memcached_st *mst,
184                               struct moxi_stats *out, int *failures)
185 {
186     int i;
187     double *timing_results = calloc(recipe.iterations, sizeof(double));
188     char *key = calloc(recipe.keysize, sizeof(char));
189     char *value = calloc(recipe.valsize, sizeof(char));
190     struct timeval timing;
191 
192     memset(&timing, 0, sizeof(timing));
193     assert(timing_results);
194     assert(key);
195     assert(value);
196 
197     /* Key is all 't's...just because */
198     memset(key, 't', recipe.keysize);
199     /* Value is a random bunch of stuff */
200     for (i = 0; i < recipe.valsize; i++) {
201         value[i] = rand() & 0xff;
202     }
203 
204     if (memcached_set(mst,
205                       key, recipe.keysize,
206                       value, recipe.valsize,
207                       0, 0) != MEMCACHED_SUCCESS) {
208         /* XXX: Failure */
209     }
210 
211     for (i = 0 ; i < recipe.iterations; i++) {
212         struct timeval tv_pre, tv_post;
213         size_t retrieved_len = 0;
214         uint32_t flags = 0;
215         memcached_return error;
216         char *retrieved;
217 
218         memset(&tv_pre, 0, sizeof(tv_pre));
219         memset(&tv_post, 0, sizeof(tv_post));
220 
221         gettimeofday(&tv_pre, NULL);
222 
223         retrieved = memcached_get(mst,
224                                   key, recipe.keysize,
225                                   &retrieved_len, &flags,
226                                   &error);
227 
228         gettimeofday(&tv_post, NULL);
229         timeval_subtract(&timing, &tv_post, &tv_pre);
230         timing_results[i] = timeval_to_double(timing);
231 
232         if (retrieved) {
233             free(retrieved);
234         } else {
235             (*failures)++;
236         }
237     }
238 
239     compute_stats(out, timing_results, recipe.iterations);
240     free(timing_results);
241     free(key);
242     free(value);
243 }
244 
ping_server(char *server_name, struct ping_test_recipe *recipes, proxy_behavior *behavior, conflate_form_result *r)245 static void ping_server(char *server_name,
246                         struct ping_test_recipe *recipes,
247                         proxy_behavior *behavior,
248                         conflate_form_result *r) {
249     memcached_st         mst;
250     memcached_server_st *mservers;
251     struct timeval timing;
252     char  buf[300] = { 0x00 };
253 
254     assert(server_name);
255     assert(behavior);
256     assert(r);
257 
258     if (strlen(behavior->host) <= 0 ||
259         behavior->port <= 0)
260         return;
261 
262     conflate_next_fieldset(r);
263     conflate_add_field(r, "-set-", server_name);
264 
265 #define dbl_report(name, dval)                  \
266     snprintf(buf, sizeof(buf), "%f", dval);     \
267     conflate_add_field(r, name, buf);
268 
269 #define int_report(name, ival)                  \
270     snprintf(buf, sizeof(buf), "%d", ival);     \
271     conflate_add_field(r, name, buf);
272 
273 #define tv_report(name, mark, val)                  \
274     timeval_subtract(&timing, &val, &mark);         \
275     dbl_report(name, timeval_to_double(timing));
276 
277 #define stat_report(buf, buflen, name, type, dval)  \
278     snprintf(buf, buflen, "%s_%s", name, type);     \
279     dbl_report(buf, dval);
280 
281     if (memcached_create(&mst) != NULL) {
282         memcached_behavior_set(&mst, MEMCACHED_BEHAVIOR_NO_BLOCK, 1);
283         memcached_behavior_set(&mst, MEMCACHED_BEHAVIOR_TCP_NODELAY, 1);
284 
285         snprintf(buf, sizeof(buf),
286                  "%s:%u",
287                  behavior->host,
288                  behavior->port);
289 
290         mservers = memcached_servers_parse(buf);
291         if (mservers != NULL) {
292             int i;
293             int nconns;
294             bool connected;
295 
296             memcached_server_push(&mst, mservers);
297             memcached_server_list_free(mservers);
298             mservers = NULL;
299 
300             nconns = memcached_server_count(&mst);
301             connected = false;
302 
303             for (i = 0; i < nconns; i++) {
304                 struct timeval start;
305                 mcs_server_st *st;
306                 mcs_return rc;
307 
308                 if (settings.verbose > 1)
309                     moxi_log_write("ping_test connecting %d\n", i);
310 
311                 gettimeofday(&start, NULL);
312 
313                 st = mcs_server_index((void *) &mst, i);
314                 rc = mcs_server_st_connect(st, NULL, true);
315                 if (rc == MCS_SUCCESS) {
316                     struct timeval tv_conn;
317                     gettimeofday(&tv_conn, NULL);
318                     tv_report("conn", start, tv_conn);
319 
320                     if (cproxy_auth_downstream(st, behavior,
321                                                mcs_server_st_fd(st)) &&
322                         cproxy_bucket_downstream(st, behavior,
323                                                  mcs_server_st_fd(st))) {
324                         struct timeval tv_auth;
325                         gettimeofday(&tv_auth, NULL);
326                         tv_report("auth", tv_conn, tv_auth);
327 
328                         /* Flag whether to proceed if we connected */
329                         connected = true;
330                     }
331                 }
332             }
333 
334             if (connected) {
335                 int j;
336                 for (j = 0; recipes[j].name; j++) {
337                     struct moxi_stats recipe_stats;
338                     int failures = 0;
339                     int vlen;
340                     char *val_name;
341 
342                     memset(&recipe_stats, 0, sizeof(recipe_stats));
343 
344                     perform_ping_test(recipes[j], &mst,
345                                       &recipe_stats, &failures);
346                     vlen = (int)strlen(recipes[j].name) + 8;
347                     val_name = malloc(vlen + 1);
348                     if (val_name != NULL) {
349                         stat_report(val_name, vlen, recipes[j].name,
350                                     "min", recipe_stats.min);
351                         stat_report(val_name, vlen, recipes[j].name,
352                                     "avg", recipe_stats.avg);
353                         stat_report(val_name, vlen, recipes[j].name,
354                                     "max", recipe_stats.max);
355                         stat_report(val_name, vlen, recipes[j].name,
356                                     "stddev", recipe_stats.stddev);
357                         stat_report(val_name, vlen, recipes[j].name,
358                                     "95th", recipe_stats.ninetyfifth);
359 
360 
361                         snprintf(val_name, vlen, "%s_fail", recipes[j].name);
362                         int_report(val_name, failures);
363                         free(val_name);
364                     }
365                 }
366             }
367         } else {
368             conflate_add_field(r, "error", "Didn't work. :(");
369         }
370 
371         memcached_free(&mst);
372     }
373 }
374