├── m4 └── .gitignore ├── Makefile.am ├── src ├── stats │ ├── Makefile.am │ ├── mcp_conn_stats.c │ └── mcp_call_stats.c ├── gen │ ├── Makefile.am │ ├── mcp_size_generator.c │ ├── mcp_conn_generator.c │ └── mcp_call_generator.c ├── Makefile.am ├── mcp_event.h ├── mcp_ecb.h ├── mcp_distribution.h ├── mcp_generator.h ├── mcp_timer.h ├── mcp_ecb.c ├── mcp_distribution.c ├── mcp_conn.h ├── mcp_generator.c ├── mcp_log.h ├── mcp_stats.h ├── mcp_event.c ├── mcp_conn.c ├── mcp_timer.c ├── mcp_call.h ├── mcp_log.c ├── mcp_core.h ├── mcp_util.h ├── mcp_core.c ├── mcp_util.c ├── mcp_stats.c ├── mcp.c └── mcp_call.c ├── ChangeLog ├── scripts ├── multi-client.sh └── run-all.sh ├── .gitignore ├── NOTICE ├── configure.ac └── README.md /m4/.gitignore: -------------------------------------------------------------------------------- 1 | libtool.m4 2 | ltoptions.m4 3 | ltsugar.m4 4 | ltversion.m4 5 | lt~obsolete.m4 6 | -------------------------------------------------------------------------------- /Makefile.am: -------------------------------------------------------------------------------- 1 | MAINTAINERCLEANFILES = Makefile.in aclocal.m4 configure config.h.in config.h.in~ stamp-h.in 2 | 3 | ACLOCAL_AMFLAGS = -I m4 4 | 5 | SUBDIRS = src 6 | 7 | EXTRA_DIST = README.md NOTICE LICENSE ChangeLog 8 | -------------------------------------------------------------------------------- /src/stats/Makefile.am: -------------------------------------------------------------------------------- 1 | MAINTAINERCLEANFILES = Makefile.in 2 | 3 | AM_CFLAGS = -Wall -Wshadow -Wconversion 4 | AM_CFLAGS += -D_GNU_SOURCE -D_XOPEN_SOURCE 5 | AM_CPPFLAGS = -I $(top_srcdir)/src 6 | 7 | noinst_LIBRARIES = libstats.a 8 | 9 | libstats_a_SOURCES = \ 10 | mcp_call_stats.c \ 11 | mcp_conn_stats.c 12 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | 2013-06-02 Manju Rajashekhar 2 | * twemperf: version 0.1.1 release 3 | fix off-by-one error 4 | 5 | 2011-12-10 Manju Rajashekhar 6 | 7 | * twemperf: version 0.1.0 release 8 | twemperf (aka mcperf) is a tool for measuring memcached server performance. 9 | 10 | -------------------------------------------------------------------------------- /src/gen/Makefile.am: -------------------------------------------------------------------------------- 1 | MAINTAINERCLEANFILES = Makefile.in 2 | 3 | AM_CFLAGS = -Wall -Wshadow -Wconversion 4 | AM_CFLAGS += -D_GNU_SOURCE -D_XOPEN_SOURCE 5 | AM_CPPFLAGS = -I $(top_srcdir)/src 6 | 7 | noinst_LIBRARIES = libgen.a 8 | 9 | libgen_a_SOURCES = \ 10 | mcp_call_generator.c \ 11 | mcp_conn_generator.c \ 12 | mcp_size_generator.c 13 | -------------------------------------------------------------------------------- /scripts/multi-client.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | MCPERF=./mcperf 4 | 5 | CLIENTS=16 6 | 7 | LOG=mcperf.log 8 | 9 | HOST=localhost 10 | PORT=11211 11 | 12 | NUM_CONNS=10 13 | CONN_RATE=10000 14 | 15 | NUM_CALLS=10000 16 | CALL_RATE=0 17 | 18 | for i in `seq $CLIENTS` 19 | do 20 | printf "Cleaning up existing log file at %s\n" $LOG.$i 21 | rm -f $LOG.$i 22 | printf "starting client %s\n" $i 23 | $MCPERF --server=$HOST --port=$PORT --client=$i/$CLIENTS --num-conns=$NUM_CONNS --conn-rate=$CONN_RATE --num-calls=$NUM_CALLS --call-rate=$CONN_RATE >> $LOG.$i 2>&1 & 24 | done 25 | 26 | 27 | printf "Waiting for all clients to finish...." 28 | wait 29 | printf "done.\n" 30 | -------------------------------------------------------------------------------- /src/Makefile.am: -------------------------------------------------------------------------------- 1 | MAINTAINERCLEANFILES = Makefile.in 2 | 3 | AM_CPPFLAGS = -D_GNU_SOURCE -D_XOPEN_SOURCE 4 | AM_CFLAGS = -Wall -Wshadow -Wconversion 5 | AM_LDFLAGS = -lm -rdynamic 6 | 7 | SUBDIRS = gen stats 8 | 9 | bin_PROGRAMS = mcperf 10 | 11 | mcperf_SOURCES = \ 12 | mcp_call.c mcp_call.h \ 13 | mcp_conn.c mcp_conn.h \ 14 | mcp_core.c mcp_core.h \ 15 | mcp_distribution.c mcp_distribution.h \ 16 | mcp_ecb.c mcp_ecb.h \ 17 | mcp_event.c mcp_event.h \ 18 | mcp_generator.c mcp_generator.h \ 19 | mcp_log.c mcp_log.h \ 20 | mcp_stats.c mcp_stats.h \ 21 | mcp_timer.c mcp_timer.h \ 22 | mcp_util.c mcp_util.h \ 23 | mcp_queue.h \ 24 | mcp.c 25 | 26 | mcperf_LDADD = $(top_builddir)/src/gen/libgen.a 27 | mcperf_LDADD += $(top_builddir)/src/stats/libstats.a 28 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.lo 3 | *.o 4 | 5 | # Compiled Dynamic libraries 6 | *.so 7 | 8 | # Compiled Static libraries 9 | *.la 10 | *.a 11 | 12 | # Compiled misc 13 | *.dep 14 | *.gcda 15 | *.gcno 16 | *.gcov 17 | 18 | # Packages 19 | *.tar.gz 20 | *.tar.bz2 21 | 22 | # Logs 23 | *.log 24 | 25 | # Temporary 26 | *.swp 27 | *.~ 28 | .cproject 29 | .project 30 | 31 | # Core and executable 32 | core* 33 | mcperf 34 | 35 | # Autotools 36 | .deps 37 | .libs 38 | 39 | /aclocal.m4 40 | /autom4te.cache 41 | /stamp-h1 42 | /autoscan.log 43 | /libtool 44 | 45 | /config/config.guess 46 | /config/config.sub 47 | /config/depcomp 48 | /config/install-sh 49 | /config/ltmain.sh 50 | /config/missing 51 | /config 52 | 53 | /config.h 54 | /config.h.in 55 | /config.h.in~ 56 | /config.log 57 | /config.status 58 | /configure.scan 59 | /configure 60 | 61 | Makefile 62 | Makefile.in 63 | -------------------------------------------------------------------------------- /scripts/run-all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | mcperf=~/workspace/twemperf/src/mcperf 4 | 5 | server=localhost 6 | port=11211 7 | 8 | num_conns=10 9 | conn_rate=10000 10 | 11 | num_calls=1000 12 | call_rate=0 13 | 14 | lb_size=100 15 | lb_size=1000 16 | 17 | prefix="abc:" 18 | 19 | niter=100 20 | 21 | for i in `seq 1 ${niter}`; do 22 | for method in "set" "get" "gets" "delete" "add" "incr" "decr" "replace" "append" "prepend"; do 23 | printf "[%d] command: %s =>\n" $i $method 24 | ${mcperf} --server=${server} --port=${port} --num-conns=${num_conns} --conn-rate=${conn_rate} --num-calls=${num_calls} --call-rate=${call_rate} --method=${method} --sizes=u${lb_size},${lb_size} --prefix=${prefix} >> /tmp/mcperf.out 2>&1 25 | done 26 | grep --color "error\|Errors:" /tmp/mcperf.out | grep -v "0" 27 | mv /tmp/mcperf.out /tmp/mcperf.out.${i} 28 | sleep 2 29 | done 30 | -------------------------------------------------------------------------------- /src/mcp_event.h: -------------------------------------------------------------------------------- 1 | /* 2 | * twemperf - a tool for measuring memcached server performance. 3 | * Copyright (C) 2011 Twitter, Inc. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef _MCP_EVENT_H_ 20 | #define _MCP_EVENT_H_ 21 | 22 | /* 23 | * A hint to the kernel that is used to size the event backing store 24 | * of a given epoll instance 25 | */ 26 | #define EVENT_SIZE_HINT 1024 27 | 28 | int event_init(struct context *ctx, int size); 29 | void event_deinit(struct context *ctx); 30 | 31 | int event_add_out(int ep, struct conn *c); 32 | int event_del_out(int ep, struct conn *c); 33 | int event_add_conn(int ep, struct conn *c); 34 | int event_del_conn(int ep, struct conn *c); 35 | 36 | int event_wait(int ep, struct epoll_event *event, int nevent, int timeout); 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /src/mcp_ecb.h: -------------------------------------------------------------------------------- 1 | /* 2 | * twemperf - a tool for measuring memcached server performance. 3 | * Copyright (C) 2011 Twitter, Inc. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef _MCP_ECB_H_ 20 | #define _MCP_ECB_H_ 21 | 22 | typedef void (*cb_t)(struct context *, event_type_t, void *, void *); 23 | 24 | struct cb { 25 | cb_t cb; /* callback (function) */ 26 | void *rarg; /* registration arg (non-local name) */ 27 | char *name; /* name */ 28 | char *file; /* filename */ 29 | int line; /* line */ 30 | }; 31 | 32 | #define MAX_NCB 4 33 | 34 | struct action { 35 | int ncb; /* # callback */ 36 | struct cb cb[MAX_NCB]; /* callback */ 37 | }; 38 | 39 | #define ecb_register(_ctx, _type, _cb, _rarg) \ 40 | _ecb_register(_ctx, _type, _cb, _rarg, #_cb, __FILE__, __LINE__) 41 | 42 | #define ecb_signal(_ctx, _type, _carg) \ 43 | _ecb_signal(_ctx, _type, _carg); 44 | 45 | void _ecb_register(struct context *ctx, event_type_t type, cb_t cb, void *rarg, char *name, char *file, int line); 46 | void _ecb_signal(struct context *ctx, event_type_t type, void *carg); 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /src/gen/mcp_size_generator.c: -------------------------------------------------------------------------------- 1 | /* 2 | * twemperf - a tool for measuring memcached server performance. 3 | * Copyright (C) 2011 Twitter, Inc. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include 20 | 21 | static int 22 | item_size_ticker(struct context *ctx, void *arg) 23 | { 24 | struct dist_info *di = arg; 25 | 26 | di->next(di); 27 | 28 | return 0; 29 | } 30 | 31 | static void 32 | trigger(struct context *ctx, event_type_t type, void *rarg, void *carg) 33 | { 34 | struct gen *g = &ctx->size_gen; 35 | struct dist_info *di = &ctx->size_dist; 36 | 37 | ASSERT(type == EVENT_GEN_SIZE_TRIGGER); 38 | 39 | /* 40 | * A size generator can only be a oneshot generator. The only way to 41 | * tick this generator is by signalling the fire event. 42 | */ 43 | gen_start(g, ctx, di, item_size_ticker, di, EVENT_GEN_SIZE_FIRE); 44 | } 45 | 46 | static void 47 | init(struct context *ctx, void *arg) 48 | { 49 | ecb_register(ctx, EVENT_GEN_SIZE_TRIGGER, trigger, NULL); 50 | } 51 | 52 | static void 53 | no_op(struct context *ctx, void *arg) 54 | { 55 | /* do nothing */ 56 | } 57 | 58 | struct load_generator size_generator = { 59 | "generate item sizes", 60 | init, 61 | no_op, 62 | no_op, 63 | no_op 64 | }; 65 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | twemperf is a tool for measuring memcached server performance. 2 | Copyright 2011 Twitter, Inc. 3 | 4 | Portions of twemperf were created and adapted from httperf: 5 | http://code.google.com/p/httperf 6 | 7 | The following copyright notice(s) were affixed to portions of this code 8 | with which this file is now or was at one time distributed 9 | and are placed here unaltered. 10 | 11 | /* 12 | * Copyright (C) 2007 Ted Bullock 13 | * Copyright (C) 2000 Hewlett-Packard Company 14 | * 15 | * This file is part of httperf, a web server performance measurment tool. 16 | * 17 | * This program is free software; you can redistribute it and/or modify it 18 | * under the terms of the GNU General Public License as published by the Free 19 | * Software Foundation; either version 2 of the License, or (at your option) 20 | * any later version. 21 | * 22 | * In addition, as a special exception, the copyright holders give permission 23 | * to link the code of this work with the OpenSSL project's "OpenSSL" library 24 | * (or with modified versions of it that use the same license as the "OpenSSL" 25 | * library), and distribute linked combinations including the two. You must 26 | * obey the GNU General Public License in all respects for all of the code 27 | * used other than "OpenSSL". If you modify this file, you may extend this 28 | * exception to your version of the file, but you are not obligated to do so. 29 | * If you do not wish to do so, delete this exception statement from your 30 | * version. 31 | * 32 | * This program is distributed in the hope that it will be useful, but WITHOUT 33 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 34 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 35 | * more details. 36 | * 37 | * You should have received a copy of the GNU General Public License along 38 | * with this program; if not, write to the Free Software Foundation, Inc., 51 39 | * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 40 | */ 41 | 42 | -------------------------------------------------------------------------------- /src/mcp_distribution.h: -------------------------------------------------------------------------------- 1 | /* 2 | * twemperf - a tool for measuring memcached server performance. 3 | * Copyright (C) 2011 Twitter, Inc. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef _MCP_DISTRIBUTION_H_ 20 | #define _MCP_DISTRIBUTION_H_ 21 | 22 | struct dist_info; 23 | 24 | typedef void (*dist_next_t)(struct dist_info *); 25 | 26 | typedef enum dist_type { 27 | DIST_NONE, /* invalid or special case */ 28 | DIST_DETERMINISTIC, /* fixed or deterministic */ 29 | DIST_UNIFORM, /* uniform over interval [min, max) */ 30 | DIST_EXPONENTIAL, /* poisson with mean */ 31 | DIST_SEQUENTIAL, /* sequential or monotonic */ 32 | DIST_SENTINEL 33 | } dist_type_t; 34 | 35 | struct dist_opt { 36 | dist_type_t type; /* distribution type */ 37 | double min; /* minimum value */ 38 | double max; /* maximum value */ 39 | }; 40 | 41 | struct dist_info { 42 | dist_type_t type; /* distribution type */ 43 | 44 | uint16_t xsubi[3]; /* erand48 seed */ 45 | double min; /* minimum value */ 46 | double max; /* maximum value */ 47 | 48 | dist_next_t next; /* next handler */ 49 | uint32_t next_id; /* next distribution value id */ 50 | double next_val; /* next distribution value */ 51 | }; 52 | 53 | void dist_init(struct dist_info *di, dist_type_t type, double min, double max, uint32_t id); 54 | 55 | #endif 56 | -------------------------------------------------------------------------------- /src/mcp_generator.h: -------------------------------------------------------------------------------- 1 | /* 2 | * twemperf - a tool for measuring memcached server performance. 3 | * Copyright (C) 2011 Twitter, Inc. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef _MCP_GENERATOR_H_ 20 | #define _MCP_GENERATOR_H_ 21 | 22 | typedef int (*gen_tick_t)(struct context *, void *); 23 | 24 | /* 25 | * Generator ticks at a rate controlled by dist_info. On every 26 | * tick, invoke tick (gen_tick_t), and compute the next time 27 | * to tick if any timer was scheduled. 28 | */ 29 | struct gen { 30 | struct context *ctx; /* owner context */ 31 | 32 | struct dist_info *di; /* dist info */ 33 | 34 | struct timer *timer; /* ticking timer */ 35 | char *tickname; /* tick who? */ 36 | gen_tick_t tick; /* tick me, baby! */ 37 | void *arg; /* opaque tick arg */ 38 | double start_time; /* start time of a tick (const) */ 39 | double next_time; /* next time to tick again */ 40 | 41 | unsigned oneshot:1; /* one-shot? */ 42 | unsigned done:1; /* done? */ 43 | }; 44 | 45 | #define gen_start(_g, _ctx, _di, _tick, _arg, _e) \ 46 | _gen_start(_g, _ctx, _di, #_tick, _tick, _arg, _e) 47 | 48 | void _gen_start(struct gen *g, struct context *ctx, struct dist_info *di, char *tickname, gen_tick_t tick, void *arg, event_type_t firing_event); 49 | void gen_stop(struct gen *g); 50 | 51 | #endif 52 | -------------------------------------------------------------------------------- /src/mcp_timer.h: -------------------------------------------------------------------------------- 1 | /* 2 | * twemperf - a tool for measuring memcached server performance. 3 | * Copyright (C) 2011 Twitter, Inc. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef _MCP_TIMER_H_ 20 | #define _MCP_TIMER_H_ 21 | 22 | /* 23 | * We choose 1 msec as the timer granularity. So we must ensure that 24 | * timer_tick() is invoked at least once every 1 msec 25 | * 26 | * 1 tick = 1 msec 27 | * 1 sec = 1000 ticks 28 | */ 29 | #define TIMER_INTERVAL (1.0 / 1000) /* in sec */ 30 | #define TIMER_TICKS_SEC (1.0 / TIMER_INTERVAL) /* in ticks */ 31 | 32 | #define TIMER_WHEEL_SIZE 4096 33 | 34 | struct timer; 35 | 36 | typedef void (*timeout_t)(struct timer *t, void *arg); 37 | 38 | struct timer { 39 | uint64_t id; /* unique id */ 40 | LIST_ENTRY(timer) tle; /* link in free q / timer wheel */ 41 | uint64_t delta; /* delay to expiry as # rounds */ 42 | timeout_t timeout; /* timeout handler */ 43 | void *arg; /* opaque data for timeout handler */ 44 | char *name; /* timeout handler name */ 45 | }; 46 | 47 | LIST_HEAD(timerhdr, timer); 48 | 49 | void timer_init(void); 50 | void timer_deinit(void); 51 | 52 | double timer_now(void); 53 | void timer_tick(void); 54 | 55 | #define timer_schedule(_timeout, _arg, _delay) \ 56 | _timer_schedule(_timeout, _arg, _delay, #_timeout) 57 | 58 | #define timer_cancel(_timer) do { \ 59 | _timer_cancel(_timer); \ 60 | _timer = NULL; \ 61 | } while (0) 62 | 63 | struct timer *_timer_schedule(timeout_t timeout, void *arg, double delay, char *name); 64 | void _timer_cancel(struct timer *t); 65 | 66 | #endif 67 | -------------------------------------------------------------------------------- /src/mcp_ecb.c: -------------------------------------------------------------------------------- 1 | /* 2 | * twemperf - a tool for measuring memcached server performance. 3 | * Copyright (C) 2011 Twitter, Inc. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include 20 | 21 | void 22 | _ecb_register(struct context *ctx, event_type_t type, cb_t cb, void *rarg, 23 | char *name, char *file, int line) 24 | { 25 | struct action *act; 26 | struct cb *c, *end; 27 | 28 | ASSERT(type < MAX_EVENT_TYPES); 29 | 30 | act = &ctx->action[type]; 31 | end = &act->cb[act->ncb]; 32 | 33 | for (c = &act->cb[0]; c < end; c++) { 34 | /* do nothing for duplicate registration */ 35 | if (c->cb == cb && c->rarg == rarg) { 36 | return; 37 | } 38 | } 39 | 40 | if (act->ncb >= MAX_NCB) { 41 | log_panic("attempted to register more than %d callbacks for event %d", 42 | MAX_NCB, type); 43 | } 44 | 45 | log_debug(LOG_VERB, "register event %d at %d with cb '%s' from %s:%d", type, 46 | act->ncb, name, file, line); 47 | 48 | c = &act->cb[act->ncb++]; 49 | 50 | c->cb = cb; 51 | c->rarg = rarg; 52 | 53 | c->name = name; 54 | c->file = file; 55 | c->line = line; 56 | } 57 | 58 | void 59 | _ecb_signal(struct context *ctx, event_type_t type, void *carg) 60 | { 61 | struct action *act; 62 | struct cb *c, *end; 63 | 64 | ASSERT(type < MAX_EVENT_TYPES); 65 | 66 | act = &ctx->action[type]; 67 | end = &act->cb[act->ncb]; 68 | 69 | /* signal registered events in FIFO order */ 70 | for (c = &act->cb[0]; c < end; c++) { 71 | log_debug(LOG_VERB, "signal event %d at %d with cb '%s' from %s:%d", 72 | type, c - act->cb, c->name, c->file, c->line); 73 | c->cb(ctx, type, c->rarg, carg); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/mcp_distribution.c: -------------------------------------------------------------------------------- 1 | /* 2 | * twemperf - a tool for measuring memcached server performance. 3 | * Copyright (C) 2011 Twitter, Inc. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include 20 | 21 | #include 22 | 23 | static void 24 | dist_next_deterministic(struct dist_info *di) 25 | { 26 | di->next_id++; 27 | di->next_val = 0.5 * (di->min + di->max); 28 | } 29 | 30 | static void 31 | dist_next_uniform(struct dist_info *di) 32 | { 33 | double lower = di->min, upper = di->max; 34 | 35 | di->next_id++; 36 | di->next_val = lower + (upper - lower) * erand48(di->xsubi); 37 | } 38 | 39 | static void 40 | dist_next_exponential(struct dist_info *di) 41 | { 42 | double mean = 0.5 * (di->min + di->max); 43 | 44 | di->next_id++; 45 | di->next_val = -mean * log(1.0 - erand48(di->xsubi)); 46 | } 47 | 48 | static void 49 | dist_next_sequential(struct dist_info *di) 50 | { 51 | di->next_id++; 52 | di->next_val = di->min++; 53 | } 54 | 55 | void 56 | dist_init(struct dist_info *di, dist_type_t type, double min, double max, 57 | uint32_t id) 58 | { 59 | di->type = type; 60 | 61 | di->xsubi[0] = (uint16_t)(0x1234 ^ id); 62 | di->xsubi[1] = (uint16_t)(0x5678 ^ (id << 8)); 63 | di->xsubi[2] = (uint16_t)(0x9abc ^ ~id); 64 | 65 | di->min = min; 66 | di->max = max; 67 | 68 | switch (di->type) { 69 | case DIST_NONE: 70 | di->next = NULL; 71 | break; 72 | 73 | case DIST_DETERMINISTIC: 74 | di->next = dist_next_deterministic; 75 | break; 76 | 77 | case DIST_UNIFORM: 78 | di->next = dist_next_uniform; 79 | break; 80 | 81 | case DIST_EXPONENTIAL: 82 | di->next = dist_next_exponential; 83 | break; 84 | 85 | case DIST_SEQUENTIAL: 86 | di->next = dist_next_sequential; 87 | break; 88 | 89 | default: 90 | NOT_REACHED(); 91 | } 92 | di->next_id = 0; 93 | di->next_val = 0.0; 94 | } 95 | -------------------------------------------------------------------------------- /src/mcp_conn.h: -------------------------------------------------------------------------------- 1 | /* 2 | * twemperf - a tool for measuring memcached server performance. 3 | * Copyright (C) 2011 Twitter, Inc. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef _MCP_CONN_H_ 20 | #define _MCP_CONN_H_ 21 | 22 | #include 23 | 24 | struct conn { 25 | STAILQ_ENTRY(conn) conn_tqe; /* link in free q */ 26 | uint64_t id; /* unique id */ 27 | struct context *ctx; /* owner context */ 28 | 29 | uint32_t ncall_sendq; /* # call send q */ 30 | struct call_tqh call_sendq; /* call send q */ 31 | uint32_t ncall_recvq; /* # call recv q */ 32 | struct call_tqh call_recvq; /* call recv q */ 33 | 34 | struct timer *watchdog; /* connection watchdog timer */ 35 | double connect_start; /* connect start in sec */ 36 | 37 | int sd; /* socket descriptor */ 38 | 39 | char buf[8 * KB]; /* conn buffer */ 40 | 41 | struct gen call_gen; /* call generator */ 42 | uint32_t ncall_created; /* # call created */ 43 | uint32_t ncall_create_failed; /* # call create failed */ 44 | uint32_t ncall_completed; /* # call completed */ 45 | 46 | err_t err; /* connection errno? */ 47 | unsigned recv_active:1; /* recv active? */ 48 | unsigned recv_ready:1; /* recv ready? */ 49 | unsigned send_active:1; /* send active? */ 50 | unsigned send_ready:1; /* send ready? */ 51 | 52 | unsigned connecting:1; /* connecting? */ 53 | unsigned connected:1; /* connected? */ 54 | unsigned eof:1; /* eof? */ 55 | }; 56 | 57 | STAILQ_HEAD(conn_tqh, conn); 58 | 59 | struct conn *conn_get(struct context *ctx); 60 | void conn_put(struct conn *conn); 61 | 62 | ssize_t conn_sendv(struct conn *conn, struct iovec *iov, int iovcnt, size_t iov_size); 63 | ssize_t conn_recv(struct conn *conn, void *buf, size_t size); 64 | 65 | void conn_init(void); 66 | void conn_deinit(void); 67 | 68 | #endif 69 | -------------------------------------------------------------------------------- /configure.ac: -------------------------------------------------------------------------------- 1 | # Define the package version numbers and the bug reporting address 2 | m4_define([MCP_MAJOR], 0) 3 | m4_define([MCP_MINOR], 1) 4 | m4_define([MCP_PATCH], 1) 5 | m4_define([MCP_BUGS], [manj@twitter.com]) 6 | 7 | # Initialize autoconf 8 | AC_PREREQ([2.64]) 9 | AC_INIT([mcperf], [MCP_MAJOR.MCP_MINOR.MCP_PATCH], [MCP_BUGS]) 10 | AC_CONFIG_SRCDIR([src/mcp.c]) 11 | AC_CONFIG_AUX_DIR([config]) 12 | AC_CONFIG_HEADERS([config.h:config.h.in]) 13 | AC_CONFIG_MACRO_DIR([m4]) 14 | 15 | # Initialize automake 16 | AM_INIT_AUTOMAKE([1.9 foreign]) 17 | 18 | # Define macro variables for the package version numbers 19 | AC_DEFINE(MCP_VERSION_MAJOR, MCP_MAJOR, [Define the major version number]) 20 | AC_DEFINE(MCP_VERSION_MINOR, MCP_MINOR, [Define the minor version number]) 21 | AC_DEFINE(MCP_VERSION_PATCH, MCP_PATCH, [Define the patch version number]) 22 | AC_DEFINE(MCP_VERSION_STRING, "MCP_MAJOR.MCP_MINOR.MCP_PATCH", [Define the version string]) 23 | 24 | # Checks for language 25 | AC_LANG([C]) 26 | 27 | # Checks for programs 28 | AC_PROG_AWK 29 | AC_PROG_CC 30 | AC_PROG_CPP 31 | AC_PROG_INSTALL 32 | AC_PROG_LN_S 33 | AC_PROG_MAKE_SET 34 | AC_PROG_RANLIB 35 | 36 | # Checks for typedefs, structures, and compiler characteristics 37 | AC_C_INLINE 38 | AC_TYPE_INT8_T 39 | AC_TYPE_INT16_T 40 | AC_TYPE_INT32_T 41 | AC_TYPE_INT64_T 42 | AC_TYPE_INTMAX_T 43 | AC_TYPE_INTPTR_T 44 | AC_TYPE_UINT8_T 45 | AC_TYPE_UINT16_T 46 | AC_TYPE_UINT32_T 47 | AC_TYPE_UINT64_T 48 | AC_TYPE_UINTMAX_T 49 | AC_TYPE_UINTPTR_T 50 | AC_TYPE_OFF_T 51 | AC_TYPE_PID_T 52 | AC_TYPE_SIZE_T 53 | AC_TYPE_SSIZE_T 54 | 55 | # Checks for header files 56 | AC_HEADER_STDBOOL 57 | AC_CHECK_HEADERS([fcntl.h float.h stddef.h stdlib.h string.h unistd.h]) 58 | AC_CHECK_HEADERS([inttypes.h stdint.h]) 59 | AC_CHECK_HEADERS([sys/ioctl.h sys/time.h sys/uio.h]) 60 | AC_CHECK_HEADERS([sys/socket.h sys/un.h netinet/in.h arpa/inet.h netdb.h]) 61 | AC_CHECK_HEADERS([sys/epoll.h], [], [AC_MSG_ERROR([required sys/epoll.h header file is missing])]) 62 | 63 | # Checks for library functions 64 | AC_FUNC_MALLOC 65 | AC_FUNC_REALLOC 66 | AC_FUNC_STRTOD 67 | AC_CHECK_FUNCS([gettimeofday memchr memmove memset socket strchr strerror strndup strtoul]) 68 | 69 | AC_CACHE_CHECK([if epoll works], [ac_cv_epoll_works], 70 | AC_TRY_RUN([ 71 | #include 72 | #include 73 | #include 74 | int 75 | main(int argc, char **argv) 76 | { 77 | int fd; 78 | 79 | fd = epoll_create(256); 80 | if (fd < 0) { 81 | perror("epoll_create:"); 82 | exit(1); 83 | } 84 | exit(0); 85 | } 86 | ], [ac_cv_epoll_works=yes], [ac_cv_epoll_works=no])) 87 | AS_IF([test "x$ac_cv_epoll_works" = "xyes"], [], [AC_MSG_FAILURE([Linux epoll(7) API is missing])]) 88 | 89 | # Checks for libraries 90 | AC_CHECK_LIB([m], [pow]) 91 | 92 | # Package options 93 | AC_MSG_CHECKING([whether to enable debug logs and asserts]) 94 | AC_ARG_ENABLE([debug], 95 | [AS_HELP_STRING( 96 | [--enable-debug], 97 | [enable debug logs and asserts @<:@default=no@:>@]) 98 | ], 99 | [], 100 | [enable_debug=no]) 101 | AS_CASE([x$enable_debug], 102 | [xyes], [AC_DEFINE([HAVE_ASSERT_PANIC], [1], 103 | [Define to 1 if panic on assert is enabled]) 104 | AC_DEFINE([HAVE_DEBUG_LOG], [1], [Define to 1 if debug log is enabled]) 105 | ], 106 | [xno], [], 107 | [AC_MSG_FAILURE([invalid value ${enable_debug} for --enable-debug])]) 108 | AC_MSG_RESULT($enable_debug) 109 | 110 | # Define Makefiles 111 | AC_CONFIG_FILES([Makefile 112 | src/Makefile 113 | src/gen/Makefile 114 | src/stats/Makefile]) 115 | 116 | # Generate the "configure" script 117 | AC_OUTPUT 118 | -------------------------------------------------------------------------------- /src/mcp_generator.c: -------------------------------------------------------------------------------- 1 | /* 2 | * twemperf - a tool for measuring memcached server performance. 3 | * Copyright (C) 2011 Twitter, Inc. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include 20 | 21 | static void 22 | gen_tick(struct timer *t, void *arg) 23 | { 24 | struct gen *g = arg; 25 | struct context *ctx = g->ctx; 26 | struct dist_info *di = g->di; 27 | double now; 28 | 29 | ASSERT(g->timer == t); 30 | 31 | /* timer are freed by the timeout handler */ 32 | g->timer = NULL; 33 | 34 | if (g->done) { 35 | gen_stop(g); 36 | return; 37 | } 38 | 39 | now = timer_now(); 40 | 41 | while (now > g->next_time) { 42 | g->done = (g->tick(ctx, g->arg) < 0) ? 1 : 0; 43 | if (g->done) { 44 | gen_stop(g); 45 | return; 46 | } 47 | 48 | di->next(di); 49 | 50 | g->next_time += di->next_val; 51 | 52 | log_debug(LOG_DEBUG, "tick '%s' in %g s", g->tickname, di->next_val); 53 | } 54 | 55 | g->timer = timer_schedule(gen_tick, g, g->next_time - now); 56 | } 57 | 58 | static void 59 | gen_fire(struct context *ctx, event_type_t type, void *rarg, void *carg) 60 | { 61 | struct gen *g = carg; 62 | 63 | ASSERT(g->oneshot); 64 | 65 | if (g->done) { 66 | return; 67 | } 68 | 69 | g->done = (g->tick(ctx, g->arg) < 0) ? 1 : 0; 70 | if (g->done) { 71 | gen_stop(g); 72 | } 73 | } 74 | 75 | void 76 | _gen_start(struct gen *g, struct context *ctx, struct dist_info *di, 77 | char *tickname, gen_tick_t tick, void *arg, 78 | event_type_t firing_event) 79 | { 80 | g->ctx = ctx; 81 | 82 | g->di = di; 83 | 84 | /* g->timer is initialized later */ 85 | g->tickname = tickname; 86 | g->tick = tick; 87 | g->arg = arg; 88 | g->start_time = timer_now(); 89 | /* g->next_time is initialized later */ 90 | 91 | /* g->done is initialized later */ 92 | g->oneshot = (firing_event != EVENT_INVALID) ? 1 : 0; 93 | 94 | /* 95 | * Generators are either periodic or one-shot. A trigger event 96 | * is used to start a generator. 97 | * 98 | * Once a periodic generator is triggered, it ticks periodically 99 | * at a specfic rate controlled by dist_info. On every tick, it 100 | * invokes tick and schedules a timer to tick again. 101 | * 102 | * Once a one-shot generator is triggered, it can only tick by 103 | * explicitly signalling the firing event, at which point tick 104 | * is invoked. 105 | */ 106 | if (g->oneshot) { 107 | ASSERT(firing_event < MAX_EVENT_TYPES); 108 | g->next_time = 0.0; 109 | g->timer = NULL; 110 | ecb_register(ctx, firing_event, gen_fire, NULL); 111 | } else { 112 | di->next(di); 113 | g->next_time = timer_now() + di->next_val; 114 | g->timer = timer_schedule(gen_tick, g, g->next_time - timer_now()); 115 | } 116 | 117 | log_debug(LOG_DEBUG, "start gen %p to tick '%s'", g, g->tickname); 118 | 119 | g->done = (g->tick(ctx, g->arg) < 0) ? 1 : 0; 120 | if (g->done) { 121 | gen_stop(g); 122 | } 123 | } 124 | 125 | void 126 | gen_stop(struct gen *g) 127 | { 128 | ASSERT(g->done); 129 | 130 | if (g->timer != NULL) { 131 | timer_cancel(g->timer); 132 | } 133 | 134 | log_debug(LOG_DEBUG, "stop gen %p to tick '%s'", g, g->tickname); 135 | } 136 | -------------------------------------------------------------------------------- /src/gen/mcp_conn_generator.c: -------------------------------------------------------------------------------- 1 | /* 2 | * twemperf - a tool for measuring memcached server performance. 3 | * Copyright (C) 2011 Twitter, Inc. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include 20 | 21 | /* 22 | * Return true if we are done making connections, otherwise 23 | * return false 24 | */ 25 | static bool 26 | make_conn_done(struct context *ctx) 27 | { 28 | if ((ctx->nconn_created + ctx->nconn_create_failed) == 29 | ctx->opt.num_conns) { 30 | return true; 31 | } 32 | 33 | return false; 34 | } 35 | 36 | static int 37 | make_conn(struct context *ctx, void *arg) 38 | { 39 | rstatus_t status; 40 | struct conn *conn; 41 | 42 | ASSERT(!make_conn_done(ctx)); 43 | 44 | conn = conn_get(ctx); 45 | if (conn == NULL) { 46 | ctx->nconn_create_failed++; 47 | goto done; 48 | } 49 | 50 | status = core_connect(ctx, conn); 51 | if (status != MCP_OK) { 52 | ctx->nconn_create_failed++; 53 | ecb_signal(ctx, EVENT_CONN_FAILED, conn); 54 | goto done; 55 | } 56 | 57 | ctx->nconn_created++; 58 | ecb_signal(ctx, EVENT_CONN_CREATED, conn); 59 | 60 | done: 61 | if (make_conn_done(ctx)) { 62 | log_debug(LOG_NOTICE, "created %"PRIu32" %"PRIu32" of %"PRIu32" " 63 | "connections", ctx->nconn_create_failed, ctx->nconn_created, 64 | ctx->opt.num_conns); 65 | if (ctx->nconn_destroyed == ctx->nconn_created) { 66 | core_stop(ctx); 67 | } 68 | return -1; 69 | } 70 | 71 | log_debug(LOG_INFO, "created %"PRIu32" %"PRIu32" of %"PRIu32" " 72 | "connections", ctx->nconn_create_failed, ctx->nconn_created, 73 | ctx->opt.num_conns); 74 | 75 | return 0; 76 | } 77 | 78 | static void 79 | destroyed(struct context *ctx, event_type_t type, void *rarg, void *carg) 80 | { 81 | struct conn *conn = carg; 82 | struct gen *g = &ctx->conn_gen; 83 | 84 | ASSERT(type == EVENT_CONN_DESTROYED); 85 | ASSERT(conn->ctx == ctx); 86 | 87 | core_close(ctx, conn); 88 | 89 | ctx->nconn_destroyed++; 90 | 91 | if (make_conn_done(ctx) && (ctx->nconn_destroyed == ctx->nconn_created)) { 92 | log_debug(LOG_NOTICE, "destroyed %"PRIu32" of %"PRIu32" of %"PRIu32" " 93 | "connections", ctx->nconn_destroyed, ctx->nconn_created, 94 | ctx->opt.num_conns); 95 | core_stop(ctx); 96 | return; 97 | } 98 | 99 | log_debug(LOG_INFO, "destroyed %"PRIu32" of %"PRIu32" of %"PRIu32" " 100 | "connections", ctx->nconn_destroyed, ctx->nconn_created, 101 | ctx->opt.num_conns); 102 | 103 | if (g->oneshot) { 104 | ecb_signal(ctx, EVENT_GEN_CONN_FIRE, g); 105 | } 106 | } 107 | 108 | static void 109 | trigger(struct context *ctx, event_type_t type, void *rarg, void *carg) 110 | { 111 | struct gen *g = &ctx->conn_gen; 112 | struct dist_info *di = &ctx->conn_dist; 113 | event_type_t firing_event = (di->type == DIST_NONE) ? EVENT_GEN_CONN_FIRE : 114 | EVENT_INVALID; 115 | 116 | ASSERT(type == EVENT_GEN_CONN_TRIGGER); 117 | 118 | gen_start(g, ctx, di, make_conn, NULL, firing_event); 119 | } 120 | 121 | static void 122 | init(struct context *ctx, void *arg) 123 | { 124 | ecb_register(ctx, EVENT_CONN_DESTROYED, destroyed, NULL); 125 | ecb_register(ctx, EVENT_GEN_CONN_TRIGGER, trigger, NULL); 126 | } 127 | 128 | static void 129 | no_op(struct context *ctx, void *arg) 130 | { 131 | /* do nothing */ 132 | } 133 | 134 | /* 135 | * Conn generator is responsible for creating and destroying connections 136 | * to a given server. A given server can have multiple connections 137 | * outstanding on it. 138 | */ 139 | struct load_generator conn_generator = { 140 | "creates connections to a server at a given rate", 141 | init, 142 | no_op, 143 | no_op, 144 | no_op 145 | }; 146 | -------------------------------------------------------------------------------- /src/gen/mcp_call_generator.c: -------------------------------------------------------------------------------- 1 | /* 2 | * twemperf - a tool for measuring memcached server performance. 3 | * Copyright (C) 2011 Twitter, Inc. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include 20 | 21 | /* 22 | * Return true if we are done issuing calls, otherwise 23 | * return false 24 | */ 25 | static bool 26 | issue_call_done(struct context *ctx, struct conn *conn) 27 | { 28 | if ((conn->ncall_created + conn->ncall_create_failed) == 29 | ctx->opt.num_calls) { 30 | return true; 31 | } 32 | 33 | return false; 34 | } 35 | 36 | static int 37 | issue_call(struct context *ctx, void *arg) 38 | { 39 | struct conn *conn = arg; 40 | struct call *call; 41 | 42 | ASSERT(!issue_call_done(ctx, conn)); 43 | 44 | call = call_get(conn); 45 | if (call == NULL) { 46 | conn->ncall_create_failed++; 47 | goto done; 48 | } 49 | 50 | call_make_req(ctx, call); 51 | 52 | /* 53 | * Enqueue call into sendq so that it can be sent later on 54 | * an out event 55 | */ 56 | STAILQ_INSERT_TAIL(&conn->call_sendq, call, call_tqe); 57 | conn->ncall_sendq++; 58 | 59 | conn->ncall_created++; 60 | 61 | ecb_signal(ctx, EVENT_CALL_ISSUE_START, call); 62 | 63 | done: 64 | if (issue_call_done(ctx, conn)) { 65 | log_debug(LOG_DEBUG, "issued %"PRIu32" %"PRIu32" of %"PRIu32" " 66 | "calls on c %"PRIu64"", conn->ncall_create_failed, 67 | conn->ncall_created, ctx->opt.num_calls, conn->id); 68 | if (conn->ncall_completed == conn->ncall_created) { 69 | ecb_signal(ctx, EVENT_CONN_DESTROYED, conn); 70 | } 71 | return -1; 72 | } 73 | 74 | log_debug(LOG_VERB, "issued %"PRIu32" %"PRIu32" of %"PRIu32" " 75 | "calls on c %"PRIu64"", conn->ncall_create_failed, 76 | conn->ncall_created, ctx->opt.num_calls, conn->id); 77 | 78 | return 0; 79 | } 80 | 81 | static void 82 | destroyed(struct context *ctx, event_type_t type, void *rarg, void *carg) 83 | { 84 | struct call *call = carg; 85 | struct conn *conn = call->conn; 86 | struct gen *g = &conn->call_gen; 87 | 88 | ASSERT(type == EVENT_CALL_DESTROYED); 89 | 90 | conn->ncall_completed++; 91 | 92 | if (issue_call_done(ctx, conn) && 93 | (conn->ncall_completed == conn->ncall_created)) { 94 | 95 | log_debug(LOG_DEBUG, "completed %"PRIu32" of %"PRIu32" of %"PRIu32" " 96 | "calls on c %"PRIu64"", conn->ncall_completed, 97 | conn->ncall_created, ctx->opt.num_calls, conn->id); 98 | 99 | ecb_signal(ctx, EVENT_CONN_DESTROYED, conn); 100 | return; 101 | } 102 | 103 | log_debug(LOG_VERB, "completed %"PRIu32" of %"PRIu32" of %"PRIu32" " 104 | "calls on c %"PRIu64"", conn->ncall_completed, 105 | conn->ncall_created, ctx->opt.num_calls, conn->id); 106 | 107 | if (g->oneshot) { 108 | ecb_signal(ctx, EVENT_GEN_CALL_FIRE, g); 109 | } 110 | } 111 | 112 | static void 113 | trigger(struct context *ctx, event_type_t type, void *rarg, void *carg) 114 | { 115 | struct conn *conn = carg; 116 | struct gen *g = &conn->call_gen; 117 | struct dist_info *di = &ctx->call_dist; 118 | event_type_t firing_event = (di->type == DIST_NONE) ? EVENT_GEN_CALL_FIRE : 119 | EVENT_INVALID; 120 | 121 | ASSERT(type == EVENT_GEN_CALL_TRIGGER); 122 | ASSERT(conn->ctx == ctx); 123 | 124 | gen_start(g, ctx, di, issue_call, conn, firing_event); 125 | } 126 | 127 | static void 128 | init(struct context *ctx, void *arg) 129 | { 130 | ecb_register(ctx, EVENT_CALL_DESTROYED, destroyed, NULL); 131 | ecb_register(ctx, EVENT_GEN_CALL_TRIGGER, trigger, NULL); 132 | } 133 | 134 | static void 135 | no_op(struct context *ctx, void *arg) 136 | { 137 | /* do nothing */ 138 | } 139 | 140 | /* 141 | * Call generator is responsible for issuing and completing calls on 142 | * a given connection. A given connection can have multiple calls 143 | * outstanding on it. 144 | */ 145 | struct load_generator call_generator = { 146 | "issue calls on a connection at a given rate", 147 | init, 148 | no_op, 149 | no_op, 150 | no_op 151 | }; 152 | -------------------------------------------------------------------------------- /src/stats/mcp_conn_stats.c: -------------------------------------------------------------------------------- 1 | /* 2 | * twemperf - a tool for measuring memcached server performance. 3 | * Copyright (C) 2011 Twitter, Inc. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include 20 | 21 | static void 22 | conn_created(struct context *ctx, event_type_t type, void *rarg, void *carg) 23 | { 24 | struct stats *stats = &ctx->stats; 25 | 26 | ASSERT(type == EVENT_CONN_CREATED); 27 | 28 | stats->nconn_created++; 29 | } 30 | 31 | static void 32 | conn_connecting(struct context *ctx, event_type_t type, void *rarg, void *carg) 33 | { 34 | struct stats *stats = &ctx->stats; 35 | struct conn *conn = carg; 36 | 37 | ASSERT(type == EVENT_CONN_CONNECTING); 38 | 39 | conn->connect_start = timer_now(); 40 | stats->nconnect_issued++; 41 | } 42 | 43 | static void 44 | conn_connected(struct context *ctx, event_type_t type, void *rarg, void *carg) 45 | { 46 | struct stats *stats = &ctx->stats; 47 | struct conn *conn = carg; 48 | double connect_time; 49 | 50 | ASSERT(type == EVENT_CONN_CONNECTED); 51 | ASSERT(conn->connect_start > 0.0); 52 | ASSERT(timer_now() >= conn->connect_start); 53 | ASSERT(conn->connected); 54 | 55 | stats->nconnect++; 56 | 57 | connect_time = timer_now() - conn->connect_start; 58 | stats->connect_sum += connect_time; 59 | stats->connect_sum2 += SQUARE(connect_time); 60 | stats->connect_min = MIN(connect_time, stats->connect_min); 61 | stats->connect_max = MAX(connect_time, stats->connect_max); 62 | 63 | stats->nconn_active++; 64 | stats->nconn_active_max = MAX(stats->nconn_active, stats->nconn_active_max); 65 | } 66 | 67 | static void 68 | conn_destroyed(struct context *ctx, event_type_t type, void *rarg, void *carg) 69 | { 70 | struct stats *stats = &ctx->stats; 71 | struct conn *conn = carg; 72 | 73 | ASSERT(type == EVENT_CONN_DESTROYED); 74 | 75 | if (conn->connected) { 76 | double connection_time; 77 | 78 | ASSERT(stats->nconn_active > 0); 79 | stats->nconn_active--; 80 | 81 | connection_time = timer_now() - conn->connect_start; 82 | stats->connection_sum += connection_time; 83 | stats->connection_sum2 += SQUARE(connection_time); 84 | stats->connection_min = MIN(connection_time, stats->connection_min); 85 | stats->connection_max = MAX(connection_time, stats->connection_max); 86 | } 87 | stats->nconn_destroyed++; 88 | } 89 | 90 | static void 91 | conn_timeout(struct context *ctx, event_type_t type, void *rarg, void *carg) 92 | { 93 | struct stats *stats = &ctx->stats; 94 | 95 | ASSERT(type == EVENT_CONN_TIMEOUT); 96 | 97 | stats->nclient_timeout++; 98 | } 99 | 100 | static void 101 | conn_failed(struct context *ctx, event_type_t type, void *rarg, void *carg) 102 | { 103 | struct stats *stats = &ctx->stats; 104 | struct conn *conn = carg; 105 | 106 | ASSERT(type == EVENT_CONN_FAILED); 107 | ASSERT(conn->ctx == ctx); 108 | 109 | switch (conn->err) { 110 | case EMFILE: 111 | stats->nsock_fdunavail++; 112 | break; 113 | 114 | case ENFILE: 115 | stats->nsock_ftabfull++; 116 | break; 117 | 118 | case ECONNREFUSED: 119 | stats->nsock_refused++; 120 | break; 121 | 122 | case EPIPE: 123 | case ECONNRESET: 124 | stats->nsock_reset++; 125 | break; 126 | 127 | case ETIMEDOUT: 128 | stats->nsock_timedout++; 129 | break; 130 | 131 | case EADDRNOTAVAIL: 132 | stats->nsock_addrunavail++; 133 | break; 134 | 135 | default: 136 | stats->nsock_other_error++; 137 | break; 138 | } 139 | } 140 | 141 | static void 142 | init(struct context *ctx, void *arg) 143 | { 144 | ecb_register(ctx, EVENT_CONN_CREATED, conn_created, NULL); 145 | ecb_register(ctx, EVENT_CONN_CONNECTING, conn_connecting, NULL); 146 | ecb_register(ctx, EVENT_CONN_CONNECTED, conn_connected, NULL); 147 | ecb_register(ctx, EVENT_CONN_DESTROYED, conn_destroyed, NULL); 148 | ecb_register(ctx, EVENT_CONN_TIMEOUT, conn_timeout, NULL); 149 | ecb_register(ctx, EVENT_CONN_FAILED, conn_failed, NULL); 150 | } 151 | 152 | static void 153 | no_op(struct context *ctx, void *arg) 154 | { 155 | /* do nothing */ 156 | } 157 | 158 | struct stats_collector conn_stats = { 159 | "collect connection related statistics", 160 | init, 161 | no_op, 162 | no_op, 163 | no_op 164 | }; 165 | -------------------------------------------------------------------------------- /src/mcp_log.h: -------------------------------------------------------------------------------- 1 | /* 2 | * twemperf - a tool for measuring memcached server performance. 3 | * Copyright (C) 2011 Twitter, Inc. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef _MCP_LOG_H_ 20 | #define _MCP_LOG_H_ 21 | 22 | struct logger { 23 | char *name; /* log file name */ 24 | int level; /* log level */ 25 | int fd; /* log file descriptor */ 26 | int nerror; /* # log error */ 27 | }; 28 | 29 | #define LOG_EMERG 0 /* system in unusable */ 30 | #define LOG_ALERT 1 /* action must be taken immediately */ 31 | #define LOG_CRIT 2 /* critical conditions */ 32 | #define LOG_ERR 3 /* error conditions */ 33 | #define LOG_WARN 4 /* warning conditions */ 34 | #define LOG_NOTICE 5 /* normal but significant condition (default) */ 35 | #define LOG_INFO 6 /* informational */ 36 | #define LOG_DEBUG 7 /* debug messages */ 37 | #define LOG_VERB 8 /* verbose messages */ 38 | #define LOG_VVERB 9 /* verbose messages on crack */ 39 | #define LOG_VVVERB 10 /* verbose messages on ganga */ 40 | #define LOG_PVERB 11 /* periodic verbose messages on crack */ 41 | 42 | #define LOG_MAX_LEN 256 /* max length of log message */ 43 | 44 | /* 45 | * log_stderr - log to stderr 46 | * loga - log always 47 | * loga_hexdump - log hexdump always 48 | * log_error - error log messages 49 | * log_warn - warning log messages 50 | * log_panic - log messages followed by a panic 51 | * ... 52 | * log_debug - debug log messages based on a log level 53 | * log_hexdump - hexadump -C of a log buffer 54 | */ 55 | #ifdef MCP_DEBUG_LOG 56 | 57 | #define log_debug(_level, ...) do { \ 58 | if (log_loggable(_level) != 0) { \ 59 | _log(__FILE__, __LINE__, 0, __VA_ARGS__); \ 60 | } \ 61 | } while (0) 62 | 63 | #define log_hexdump(_level, _data, _datalen, ...) do { \ 64 | if (log_loggable(_level) != 0) { \ 65 | _log_hexdump(__FILE__, __LINE__, (char *)(_data), (int)(_datalen), \ 66 | __VA_ARGS__); \ 67 | } \ 68 | } while (0) 69 | 70 | #else 71 | 72 | #define log_debug(_level, ...) 73 | #define log_hexdump(_level, _data, _datalen, ...) 74 | 75 | #endif 76 | 77 | #define log_stderr(...) do { \ 78 | _log_stderr(__VA_ARGS__); \ 79 | } while (0) 80 | 81 | #define loga(...) do { \ 82 | _log(__FILE__, __LINE__, 0, __VA_ARGS__); \ 83 | } while (0) 84 | 85 | #define loga_hexdump(_data, _datalen, ...) do { \ 86 | _log_hexdump(__FILE__, __LINE__, (char *)(_data), (int)(_datalen), \ 87 | __VA_ARGS__); \ 88 | } while (0) \ 89 | 90 | #define log_error(...) do { \ 91 | if (log_loggable(LOG_ALERT) != 0) { \ 92 | _log(__FILE__, __LINE__, 0, __VA_ARGS__); \ 93 | } \ 94 | } while (0) 95 | 96 | #define log_warn(...) do { \ 97 | if (log_loggable(LOG_WARN) != 0) { \ 98 | _log(__FILE__, __LINE__, 0, __VA_ARGS__); \ 99 | } \ 100 | } while (0) 101 | 102 | #define log_panic(...) do { \ 103 | if (log_loggable(LOG_EMERG) != 0) { \ 104 | _log(__FILE__, __LINE__, 1, __VA_ARGS__); \ 105 | } \ 106 | } while (0) 107 | 108 | int log_init(int level, char *filename); 109 | void log_deinit(void); 110 | void log_level_up(void); 111 | void log_level_down(void); 112 | void log_level_set(int level); 113 | void log_reopen(void); 114 | int log_loggable(int level); 115 | void _log(const char *file, int line, int panic, const char *fmt, ...); 116 | void _log_stderr(const char *fmt, ...); 117 | void _log_hexdump(const char *file, int line, char *data, int datalen, const char *fmt, ...); 118 | 119 | #endif 120 | -------------------------------------------------------------------------------- /src/mcp_stats.h: -------------------------------------------------------------------------------- 1 | /* 2 | * twemperf - a tool for measuring memcached server performance. 3 | * Copyright (C) 2011 Twitter, Inc. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef _MCP_STATS_H_ 20 | #define _MCP_STATS_H_ 21 | 22 | #include 23 | 24 | #define HIST_MAX_TIME 100 /* max time in sec (histogram resolution) */ 25 | #define HIST_BIN_WIDTH 1e-3 /* bin width in sec (granularity) */ 26 | #define HIST_NUM_BINS (HIST_MAX_TIME * 1000) /* # bins */ 27 | 28 | struct stats { 29 | struct rusage rusage_start; /* resource usage at start */ 30 | struct rusage rusage_stop; /* resource usage at end */ 31 | 32 | double start_time; /* test start time in sec */ 33 | double stop_time; /* test end time in sec */ 34 | 35 | uint32_t nconn_created; /* # connection created */ 36 | uint32_t nconn_destroyed; /* # connection destroyed */ 37 | 38 | uint32_t nconn_active; /* # connection active */ 39 | uint32_t nconn_active_max; /* max # connection active */ 40 | 41 | uint32_t nconnect_issued; /* # connect issued */ 42 | uint32_t nconnect; /* # successful connect */ 43 | double connect_sum; /* sum of connect time in sec */ 44 | double connect_sum2; /* sum of connect time squared in sec^2 */ 45 | double connect_min; /* min connect time in sec */ 46 | double connect_max; /* max connect time in sec */ 47 | double connection_sum; /* sum of connection time in sec */ 48 | double connection_sum2; /* sum of connection time squared in sec^2 */ 49 | double connection_min; /* min connection time in sec */ 50 | double connection_max; /* max connection time in sec */ 51 | 52 | uint32_t nclient_timeout; /* # client timeout */ 53 | uint32_t nsock_fdunavail; /* # out of file descriptors */ 54 | uint32_t nsock_ftabfull; /* # file table overflow */ 55 | uint32_t nsock_addrunavail; /* # EADDRNOTAVAIL */ 56 | uint32_t nsock_refused; /* # ECONNREFUSED */ 57 | uint32_t nsock_reset; /* # ECONNRESET or EPIPE */ 58 | uint32_t nsock_timedout; /* # ETIMEDOUT */ 59 | uint32_t nsock_other_error; /* # other errors */ 60 | 61 | uint32_t nreq; /* # request sent */ 62 | double req_bytes_sent; /* request bytes sent */ 63 | double req_bytes_sent2; /* request bytes sent squared */ 64 | double req_bytes_sent_min; /* min request bytes sent */ 65 | double req_bytes_sent_max; /* max request bytes sent */ 66 | 67 | double req_xfer_sum; /* sum of request transfer time in sec */ 68 | double req_xfer_sum2; /* sum of request transfer time squared in sec^2 */ 69 | double req_xfer_min; /* min request transfer time in sec */ 70 | double req_xfer_max; /* max request transfer time in sec */ 71 | 72 | double req_rsp_sum; /* sum of request to response time in sec */ 73 | double req_rsp_sum2; /* sum of request to response time squared in sec^2 */ 74 | double req_rsp_min; /* min request to response time in sec */ 75 | double req_rsp_max; /* max request to response time in sec */ 76 | long int req_rsp_hist[HIST_NUM_BINS]; /* histogram of request to response time */ 77 | 78 | uint32_t nrsp; /* # responses received */ 79 | double rsp_bytes_rcvd; /* bytes received */ 80 | double rsp_bytes_rcvd2; /* bytes received squared */ 81 | double rsp_bytes_rcvd_min; /* min bytes received */ 82 | double rsp_bytes_rcvd_max; /* max bytes received */ 83 | 84 | double rsp_xfer_sum; /* sum of response transfer sum */ 85 | double rsp_xfer_sum2; /* sum of response transfer sum squared */ 86 | double rsp_xfer_min; /* minimum response transfer time */ 87 | double rsp_xfer_max; /* maximum response transfer time */ 88 | 89 | uint32_t rsp_type[RSP_MAX_TYPES]; /* # response type */ 90 | }; 91 | 92 | void stats_init(struct context *ctx); 93 | void stats_start(struct context *ctx); 94 | void stats_stop(struct context *ctx); 95 | void stats_dump(struct context *ctx); 96 | 97 | #endif 98 | -------------------------------------------------------------------------------- /src/stats/mcp_call_stats.c: -------------------------------------------------------------------------------- 1 | /* 2 | * twemperf - a tool for measuring memcached server performance. 3 | * Copyright (C) 2011 Twitter, Inc. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include 20 | 21 | static void 22 | call_created(struct context *ctx, event_type_t type, void *rarg, void *carg) 23 | { 24 | ASSERT(type == EVENT_CALL_CREATED); 25 | } 26 | 27 | static void 28 | call_issue_start(struct context *ctx, event_type_t type, void *rarg, void *carg) 29 | { 30 | struct call *call = carg; 31 | 32 | ASSERT(type == EVENT_CALL_ISSUE_START); 33 | 34 | call->req.issue_start = timer_now(); 35 | } 36 | 37 | static void 38 | call_send_start(struct context *ctx, event_type_t type, void *rarg, void *carg) 39 | { 40 | struct call *call = carg; 41 | 42 | ASSERT(type == EVENT_CALL_SEND_START); 43 | ASSERT(call->req.issue_start > 0.0); 44 | 45 | call->req.send_start = timer_now(); 46 | } 47 | 48 | static void 49 | call_send_stop(struct context *ctx, event_type_t type, void *rarg, void *carg) 50 | { 51 | struct stats *stats = &ctx->stats; 52 | struct call *call = carg; 53 | double req_xfer_time; 54 | 55 | ASSERT(type == EVENT_CALL_SEND_STOP); 56 | ASSERT(call->req.sent > 0); 57 | ASSERT(call->req.send_start > 0.0); 58 | ASSERT(call->req.send_start >= call->req.issue_start); 59 | 60 | call->req.send_stop = timer_now(); 61 | 62 | stats->nreq++; 63 | 64 | stats->req_bytes_sent += call->req.sent; 65 | stats->req_bytes_sent2 += SQUARE(call->req.sent); 66 | stats->req_bytes_sent_min = MIN(call->req.sent, stats->req_bytes_sent_min); 67 | stats->req_bytes_sent_max = MAX(call->req.sent, stats->req_bytes_sent_max); 68 | 69 | req_xfer_time = timer_now() - call->req.send_start; 70 | stats->req_xfer_sum += req_xfer_time; 71 | stats->req_xfer_sum2 += SQUARE(req_xfer_time); 72 | stats->req_xfer_min = MIN(req_xfer_time, stats->req_xfer_min); 73 | stats->req_xfer_max = MAX(req_xfer_time, stats->req_xfer_max); 74 | } 75 | 76 | static void 77 | call_recv_start(struct context *ctx, event_type_t type, void *rarg, void *carg) 78 | { 79 | struct stats *stats = &ctx->stats; 80 | struct call *call = carg; 81 | double req_rsp_time; 82 | long int bin; 83 | 84 | ASSERT(type == EVENT_CALL_RECV_START); 85 | 86 | call->rsp.recv_start = timer_now(); 87 | req_rsp_time = timer_now() - call->req.send_start; 88 | 89 | stats->req_rsp_sum += req_rsp_time; 90 | stats->req_rsp_sum2 += SQUARE(req_rsp_time); 91 | stats->req_rsp_min = MIN(req_rsp_time, stats->req_rsp_min); 92 | stats->req_rsp_max = MAX(req_rsp_time, stats->req_rsp_max); 93 | 94 | bin = MIN(lrint(req_rsp_time / HIST_BIN_WIDTH), HIST_NUM_BINS - 1); 95 | stats->req_rsp_hist[bin]++; 96 | } 97 | 98 | static void 99 | call_recv_stop(struct context *ctx, event_type_t type, void *rarg, void *carg) 100 | { 101 | struct stats *stats = &ctx->stats; 102 | struct call *call = carg; 103 | double rsp_xfer_time; 104 | 105 | ASSERT(type == EVENT_CALL_RECV_STOP); 106 | ASSERT(call->rsp.type < RSP_MAX_TYPES); 107 | 108 | stats->rsp_type[call->rsp.type]++; 109 | stats->nrsp++; 110 | 111 | stats->rsp_bytes_rcvd += call->rsp.rcvd; 112 | stats->rsp_bytes_rcvd2 += SQUARE(call->rsp.rcvd); 113 | stats->rsp_bytes_rcvd_min = MIN(call->rsp.rcvd, stats->rsp_bytes_rcvd_min); 114 | stats->rsp_bytes_rcvd_max = MAX(call->rsp.rcvd, stats->rsp_bytes_rcvd_max); 115 | 116 | rsp_xfer_time = timer_now() - call->rsp.recv_start; 117 | stats->rsp_xfer_sum += rsp_xfer_time; 118 | stats->rsp_xfer_sum2 += SQUARE(rsp_xfer_time); 119 | stats->rsp_xfer_min = MIN(rsp_xfer_time, stats->rsp_xfer_min); 120 | stats->rsp_xfer_max = MAX(rsp_xfer_time, stats->rsp_xfer_max); 121 | } 122 | 123 | static void 124 | call_destroyed(struct context *ctx, event_type_t type, void *rarg, void *carg) 125 | { 126 | ASSERT(type == EVENT_CALL_DESTROYED); 127 | } 128 | 129 | static void 130 | init(struct context *ctx, void *arg) 131 | { 132 | ecb_register(ctx, EVENT_CALL_CREATED, call_created, NULL); 133 | ecb_register(ctx, EVENT_CALL_ISSUE_START, call_issue_start, NULL); 134 | ecb_register(ctx, EVENT_CALL_SEND_START, call_send_start, NULL); 135 | ecb_register(ctx, EVENT_CALL_SEND_STOP, call_send_stop, NULL); 136 | ecb_register(ctx, EVENT_CALL_RECV_START, call_recv_start, NULL); 137 | ecb_register(ctx, EVENT_CALL_RECV_STOP, call_recv_stop, NULL); 138 | ecb_register(ctx, EVENT_CALL_DESTROYED, call_destroyed, NULL); 139 | } 140 | 141 | static void 142 | no_op(struct context *ctx, void *arg) 143 | { 144 | /* do nothing */ 145 | } 146 | 147 | struct stats_collector call_stats = { 148 | "collect message related statistics", 149 | init, 150 | no_op, 151 | no_op, 152 | no_op 153 | }; 154 | -------------------------------------------------------------------------------- /src/mcp_event.c: -------------------------------------------------------------------------------- 1 | /* 2 | * twemperf - a tool for measuring memcached server performance. 3 | * Copyright (C) 2011 Twitter, Inc. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include 20 | #include 21 | 22 | #include 23 | 24 | int 25 | event_init(struct context *ctx, int size) 26 | { 27 | int status, ep; 28 | struct epoll_event *event; 29 | 30 | ASSERT(ctx->nevent != 0); 31 | 32 | ep = epoll_create(size); 33 | if (ep < 0) { 34 | log_error("epoll create of size %d failed: %s", size, strerror(errno)); 35 | return -1; 36 | } 37 | 38 | event = mcp_calloc(ctx->nevent, sizeof(*ctx->event)); 39 | if (event == NULL) { 40 | status = close(ep); 41 | if (status < 0) { 42 | log_error("close e %d failed, ignored: %s", ep, strerror(errno)); 43 | } 44 | log_error("epoll create of %d events failed: %s", ctx->nevent, 45 | strerror(errno)); 46 | return -1; 47 | } 48 | 49 | ctx->ep = ep; 50 | ctx->event = event; 51 | 52 | log_debug(LOG_DEBUG, "e %d with nevent %d timeout %d", ctx->ep, 53 | ctx->nevent, ctx->timeout); 54 | 55 | return 0; 56 | } 57 | 58 | void 59 | event_deinit(struct context *ctx) 60 | { 61 | int status; 62 | 63 | ASSERT(ctx->ep > 0); 64 | 65 | mcp_free(ctx->event); 66 | 67 | status = close(ctx->ep); 68 | if (status < 0) { 69 | log_error("close e %d failed, ignored: %s", ctx->ep, strerror(errno)); 70 | } 71 | ctx->ep = -1; 72 | } 73 | 74 | int 75 | event_add_out(int ep, struct conn *c) 76 | { 77 | int status; 78 | struct epoll_event event; 79 | 80 | ASSERT(ep > 0); 81 | ASSERT(c != NULL); 82 | ASSERT(c->sd > 0); 83 | ASSERT(c->recv_active); 84 | 85 | if (c->send_active) { 86 | return 0; 87 | } 88 | 89 | //event.events = (uint32_t)(EPOLLIN | EPOLLOUT | EPOLLET); 90 | event.events = (uint32_t)(EPOLLIN | EPOLLOUT); 91 | event.data.ptr = c; 92 | 93 | status = epoll_ctl(ep, EPOLL_CTL_MOD, c->sd, &event); 94 | if (status < 0) { 95 | log_error("epoll ctl on e %d sd %d failed: %s", ep, c->sd, 96 | strerror(errno)); 97 | } else { 98 | c->send_active = 1; 99 | } 100 | 101 | return status; 102 | } 103 | 104 | int 105 | event_del_out(int ep, struct conn *c) 106 | { 107 | int status; 108 | struct epoll_event event; 109 | 110 | ASSERT(ep > 0); 111 | ASSERT(c != NULL); 112 | ASSERT(c->sd > 0); 113 | ASSERT(c->recv_active); 114 | 115 | if (!c->send_active) { 116 | return 0; 117 | } 118 | 119 | //event.events = (uint32_t)(EPOLLIN | EPOLLET); 120 | event.events = (uint32_t)(EPOLLIN); 121 | event.data.ptr = c; 122 | 123 | status = epoll_ctl(ep, EPOLL_CTL_MOD, c->sd, &event); 124 | if (status < 0) { 125 | log_error("epoll ctl on e %d sd %d failed: %s", ep, c->sd, 126 | strerror(errno)); 127 | } else { 128 | c->send_active = 0; 129 | } 130 | 131 | return status; 132 | } 133 | 134 | int 135 | event_add_conn(int ep, struct conn *c) 136 | { 137 | int status; 138 | struct epoll_event event; 139 | 140 | ASSERT(ep > 0); 141 | ASSERT(c != NULL); 142 | ASSERT(c->sd > 0); 143 | 144 | //event.events = (uint32_t)(EPOLLIN | EPOLLOUT | EPOLLET); 145 | event.events = (uint32_t)(EPOLLIN | EPOLLOUT); 146 | event.data.ptr = c; 147 | 148 | status = epoll_ctl(ep, EPOLL_CTL_ADD, c->sd, &event); 149 | if (status < 0) { 150 | log_error("epoll ctl on e %d sd %d failed: %s", ep, c->sd, 151 | strerror(errno)); 152 | } else { 153 | c->send_active = 1; 154 | c->recv_active = 1; 155 | } 156 | 157 | return status; 158 | } 159 | 160 | int 161 | event_del_conn(int ep, struct conn *c) 162 | { 163 | int status; 164 | 165 | ASSERT(ep > 0); 166 | ASSERT(c != NULL); 167 | ASSERT(c->sd > 0); 168 | 169 | status = epoll_ctl(ep, EPOLL_CTL_DEL, c->sd, NULL); 170 | if (status < 0) { 171 | log_error("epoll ctl on e %d sd %d failed: %s", ep, c->sd, 172 | strerror(errno)); 173 | } else { 174 | c->recv_active = 0; 175 | c->send_active = 0; 176 | } 177 | 178 | return status; 179 | } 180 | 181 | int 182 | event_wait(int ep, struct epoll_event *event, int nevent, int timeout) 183 | { 184 | int nsd; 185 | 186 | ASSERT(ep > 0); 187 | ASSERT(event != NULL); 188 | ASSERT(nevent > 0); 189 | 190 | for (;;) { 191 | nsd = epoll_wait(ep, event, nevent, timeout); 192 | if (nsd > 0) { 193 | return nsd; 194 | } 195 | 196 | if (nsd == 0) { 197 | if (timeout == -1) { 198 | log_error("epoll wait on e %d with %d events and %d timeout " 199 | "returned no events", ep, nevent, timeout); 200 | return -1; 201 | } 202 | 203 | return 0; 204 | } 205 | 206 | if (errno == EINTR) { 207 | continue; 208 | } 209 | 210 | log_error("epoll wait on e %d with %d events failed: %s", ep, nevent, 211 | strerror(errno)); 212 | 213 | return -1; 214 | } 215 | 216 | NOT_REACHED(); 217 | } 218 | -------------------------------------------------------------------------------- /src/mcp_conn.c: -------------------------------------------------------------------------------- 1 | /* 2 | * twemperf - a tool for measuring memcached server performance. 3 | * Copyright (C) 2011 Twitter, Inc. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include 20 | 21 | #include 22 | 23 | static int nfree_connq; /* # free conn q */ 24 | static struct conn_tqh free_connq; /* free conn q */ 25 | static uint64_t id; 26 | 27 | struct conn * 28 | conn_get(struct context *ctx) 29 | { 30 | struct conn *conn; 31 | 32 | if (!STAILQ_EMPTY(&free_connq)) { 33 | ASSERT(nfree_connq > 0); 34 | 35 | conn = STAILQ_FIRST(&free_connq); 36 | nfree_connq--; 37 | 38 | STAILQ_REMOVE_HEAD(&free_connq, conn_tqe); 39 | } else { 40 | conn = mcp_alloc(sizeof(*conn)); 41 | if (conn == NULL) { 42 | return NULL; 43 | } 44 | } 45 | 46 | STAILQ_NEXT(conn, conn_tqe) = NULL; 47 | conn->id = ++id; 48 | conn->ctx = ctx; 49 | 50 | conn->ncall_sendq = 0; 51 | STAILQ_INIT(&conn->call_sendq); 52 | conn->ncall_recvq = 0; 53 | STAILQ_INIT(&conn->call_recvq); 54 | 55 | conn->watchdog = NULL; 56 | conn->connect_start = 0.0; 57 | 58 | conn->sd = -1; 59 | 60 | /* conn->call_gen is initialized later */ 61 | conn->ncall_created = 0; 62 | conn->ncall_create_failed = 0; 63 | conn->ncall_completed = 0; 64 | 65 | conn->err = 0; 66 | conn->recv_active = 0; 67 | conn->recv_ready = 0; 68 | conn->send_active = 0; 69 | conn->send_ready = 0; 70 | 71 | conn->connecting = 0; 72 | conn->connected = 0; 73 | conn->eof = 0; 74 | 75 | log_debug(LOG_VVERB, "get conn %p id %"PRIu64"", conn, conn->id); 76 | 77 | return conn; 78 | } 79 | 80 | void 81 | conn_put(struct conn *conn) 82 | { 83 | log_debug(LOG_VVERB, "put conn %p id %"PRIu64"", conn, conn->id); 84 | 85 | nfree_connq++; 86 | STAILQ_INSERT_TAIL(&free_connq, conn, conn_tqe); 87 | } 88 | 89 | static void 90 | conn_free(struct conn *conn) 91 | { 92 | log_debug(LOG_VVERB, "free conn %p id %"PRIu64"", conn, conn->id); 93 | mcp_free(conn); 94 | } 95 | 96 | ssize_t 97 | conn_sendv(struct conn *conn, struct iovec *iov, int iovcnt, size_t iov_size) 98 | { 99 | ssize_t n; 100 | 101 | ASSERT(iov_size != 0); 102 | ASSERT(conn->send_ready); 103 | 104 | for (;;) { 105 | n = writev(conn->sd, iov, iovcnt); 106 | 107 | log_debug(LOG_VERB, "sendv on c %"PRIu64" sd %d %zd of %zu in " 108 | "%"PRIu32" buffers", conn->id, conn->sd, n, iov_size, 109 | iovcnt); 110 | 111 | if (n > 0) { 112 | if (n < (ssize_t) iov_size) { 113 | conn->send_ready = 0; 114 | } 115 | return n; 116 | } 117 | 118 | if (n == 0) { 119 | log_warn("sendv on c %"PRIu64" sd %d returned zero", conn->id, 120 | conn->sd); 121 | conn->send_ready = 0; 122 | return 0; 123 | } 124 | 125 | if (errno == EINTR) { 126 | log_debug(LOG_VERB, "sendv on c %"PRIu64" sd %d not ready - eintr", 127 | conn->id, conn->sd); 128 | continue; 129 | } else if (errno == EAGAIN || errno == EWOULDBLOCK) { 130 | conn->send_ready = 0; 131 | log_debug(LOG_VERB, "sendv on c %"PRIu64" sd %d not ready - " 132 | "eagain", conn->id, conn->sd); 133 | return MCP_EAGAIN; 134 | } else { 135 | conn->send_ready = 0; 136 | conn->err = errno; 137 | log_debug(LOG_ERR, "sendv on c %"PRIu64" sd %d failed: %s", 138 | conn->id, conn->sd, strerror(errno)); 139 | return MCP_EAGAIN; 140 | } 141 | } 142 | 143 | NOT_REACHED(); 144 | } 145 | 146 | ssize_t 147 | conn_recv(struct conn *conn, void *buf, size_t size) 148 | { 149 | ssize_t n; 150 | 151 | ASSERT(buf != NULL); 152 | ASSERT(size > 0); 153 | ASSERT(conn->recv_ready); 154 | 155 | for (;;) { 156 | n = read(conn->sd, buf, size); 157 | 158 | log_debug(LOG_VERB, "recv on sd %d %zd of %zu", conn->sd, n, size); 159 | 160 | if (n > 0) { 161 | if (n < (ssize_t) size) { 162 | conn->recv_ready = 0; 163 | } 164 | return n; 165 | } 166 | 167 | if (n == 0) { 168 | conn->recv_ready = 0; 169 | conn->eof = 1; 170 | log_debug(LOG_INFO, "recv on sd %d eof", conn->sd); 171 | return n; 172 | } 173 | 174 | if (errno == EINTR) { 175 | log_debug(LOG_VERB, "recv on sd %d not ready - eintr", conn->sd); 176 | continue; 177 | } else if (errno == EAGAIN || errno == EWOULDBLOCK) { 178 | conn->recv_ready = 0; 179 | log_debug(LOG_VERB, "recv on sd %d not ready - eagain", conn->sd); 180 | return MCP_EAGAIN; 181 | } else { 182 | conn->recv_ready = 0; 183 | conn->err = errno; 184 | log_error("recv on sd %d failed: %s", conn->sd, strerror(errno)); 185 | return MCP_ERROR; 186 | } 187 | } 188 | 189 | NOT_REACHED(); 190 | } 191 | 192 | void 193 | conn_init(void) 194 | { 195 | nfree_connq = 0; 196 | STAILQ_INIT(&free_connq); 197 | } 198 | 199 | void 200 | conn_deinit(void) 201 | { 202 | struct conn *conn, *nconn; /* current and next connection */ 203 | 204 | for (conn = STAILQ_FIRST(&free_connq); conn != NULL; 205 | conn = nconn, nfree_connq--) { 206 | ASSERT(nfree_connq > 0); 207 | nconn = STAILQ_NEXT(conn, conn_tqe); 208 | conn_free(conn); 209 | } 210 | ASSERT(nfree_connq == 0); 211 | } 212 | -------------------------------------------------------------------------------- /src/mcp_timer.c: -------------------------------------------------------------------------------- 1 | /* 2 | * twemperf - a tool for measuring memcached server performance. 3 | * Copyright (C) 2011 Twitter, Inc. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include 20 | 21 | #include 22 | 23 | /* 24 | * Timer wheel with TIMER_WHEEL_SIZE spokes. Each spoke represents a time 25 | * unit which equals TIMER_INTERVAL. 26 | * 27 | * With a 1 msec timer granularity and a wheel with 4096 spokes, it takes 28 | * ~4.1 seconds for the cursor to cycle back in single round. 29 | */ 30 | static struct timerhdr wheel[TIMER_WHEEL_SIZE]; /* timer wheel */ 31 | static uint32_t widx; /* current timer wheel idx */ 32 | 33 | static uint32_t nfree_timerq; /* # free timer q */ 34 | static struct timerhdr free_timerq; /* free timer q */ 35 | 36 | static double now; /* current time in sec */ 37 | static double next_tick; /* next time to tick again in sec */ 38 | 39 | static uint64_t id; /* unique id */ 40 | 41 | static struct timer * 42 | timer_get(void) 43 | { 44 | struct timer *t; 45 | 46 | if (!LIST_EMPTY(&free_timerq)) { 47 | ASSERT(nfree_timerq > 0); 48 | 49 | t = LIST_FIRST(&free_timerq); 50 | nfree_timerq--; 51 | LIST_REMOVE(t, tle); 52 | } else { 53 | t = mcp_alloc(sizeof(*t)); 54 | if (t == NULL) { 55 | return NULL; 56 | } 57 | } 58 | t->id = ++id; 59 | t->delta = 0; 60 | t->timeout = NULL; 61 | t->arg = NULL; 62 | 63 | log_debug(LOG_VERB, "get timer %p id %"PRIu64"", t, t->id); 64 | 65 | return t; 66 | } 67 | 68 | static void 69 | timer_put(struct timer *t) 70 | { 71 | log_debug(LOG_VERB, "put timer %p id %"PRIu64"", t, t->id); 72 | 73 | LIST_INSERT_HEAD(&free_timerq, t, tle); 74 | nfree_timerq++; 75 | } 76 | 77 | static void 78 | timer_now_update(void) 79 | { 80 | int status; 81 | struct timeval tv; 82 | 83 | status = gettimeofday(&tv, NULL); 84 | if (status < 0) { 85 | log_panic("gettimeofday failed: %s", strerror(errno)); 86 | } 87 | 88 | now = TV_TO_SEC(&tv); 89 | } 90 | 91 | double 92 | timer_now(void) 93 | { 94 | return now; 95 | } 96 | 97 | void 98 | timer_init(void) 99 | { 100 | uint32_t i; 101 | 102 | for (i = 0; i < TIMER_WHEEL_SIZE; i++) { 103 | LIST_INIT(&wheel[i]); 104 | } 105 | widx = 0; 106 | 107 | nfree_timerq = 0; 108 | LIST_INIT(&free_timerq); 109 | 110 | timer_now_update(); 111 | 112 | next_tick = timer_now() + TIMER_INTERVAL; 113 | } 114 | 115 | void 116 | timer_deinit(void) 117 | { 118 | } 119 | 120 | void 121 | timer_tick(void) 122 | { 123 | struct timer *t, *t_next; /* current and next timer */ 124 | 125 | timer_now_update(); 126 | 127 | while (timer_now() >= next_tick) { 128 | 129 | /* expire timed out timers in this slot */ 130 | for (t = LIST_FIRST(&wheel[widx]); t != NULL && t->delta == 0; 131 | t = t_next) { 132 | t_next = LIST_NEXT(t, tle); 133 | 134 | log_debug(LOG_DEBUG, "fire timer %"PRIu64" '%s'", t->id, t->name); 135 | 136 | (t->timeout)(t, t->arg); 137 | 138 | LIST_REMOVE(t, tle); 139 | 140 | timer_put(t); 141 | } 142 | 143 | if (t != NULL) { 144 | t->delta--; 145 | log_debug(LOG_DEBUG, "decrementing timer %"PRIu64" '%s' delta to " 146 | "%"PRIu64"", t->id, t->name, t->delta); 147 | } 148 | 149 | next_tick += TIMER_INTERVAL; 150 | widx = (widx + 1) % TIMER_WHEEL_SIZE; 151 | } 152 | } 153 | 154 | struct timer * 155 | _timer_schedule(timeout_t timeout, void *arg, double delay, char *name) 156 | { 157 | struct timerhdr *spoke; /* spoke on the wheel */ 158 | uint32_t sidx; /* spoke index */ 159 | struct timer *t, *t_new; /* current and new timer */ 160 | uint64_t ticks, delta; 161 | double behind; 162 | 163 | t_new = timer_get(); 164 | if (t_new == NULL) { 165 | return NULL; 166 | } 167 | t_new->timeout = timeout; 168 | t_new->arg = arg; 169 | t_new->name = name; 170 | 171 | behind = (timer_now() - next_tick); 172 | if (behind > 0.0) { 173 | delay += behind; 174 | } 175 | 176 | /* delay to ticks */ 177 | ticks = (uint64_t)((delay + (TIMER_INTERVAL / 2.0)) * TIMER_TICKS_SEC); 178 | if (ticks == 0) { 179 | ticks = 1; /* minimum delay is a tick */ 180 | } 181 | 182 | /* spoke for the new timer */ 183 | sidx = (widx + ticks) % TIMER_WHEEL_SIZE; 184 | spoke = &wheel[sidx]; 185 | 186 | /* # rounds around the wheel to expire the new timer */ 187 | delta = ticks / TIMER_WHEEL_SIZE; 188 | 189 | for (t = LIST_FIRST(spoke); t != NULL && delta > t->delta; 190 | t = LIST_NEXT(t, tle)) { 191 | delta -= t->delta; 192 | } 193 | 194 | t_new->delta = delta; 195 | 196 | if (t == NULL) { 197 | LIST_INSERT_HEAD(spoke, t_new, tle); 198 | } else { 199 | LIST_INSERT_AFTER(t, t_new, tle); 200 | } 201 | 202 | if (LIST_NEXT(t_new, tle) != NULL) { 203 | LIST_NEXT(t_new, tle)->delta -= delta; 204 | } 205 | 206 | log_debug(LOG_DEBUG, "schedule timer %"PRIu64" '%s' to fire after %g s, " 207 | "%"PRIu64" ticks and %"PRIu64" rounds", t_new->id, t_new->name, 208 | delay, ticks, t_new->delta); 209 | 210 | return t_new; 211 | } 212 | 213 | void 214 | _timer_cancel(struct timer *t) 215 | { 216 | struct timer *t_next; 217 | 218 | log_debug(LOG_DEBUG, "cancel timer %"PRIu64" '%s'", t->id, t->name); 219 | 220 | t_next = LIST_NEXT(t, tle); 221 | if (t_next != NULL) { 222 | t_next->delta += t->delta; 223 | } 224 | 225 | LIST_REMOVE(t, tle); 226 | 227 | timer_put(t); 228 | } 229 | -------------------------------------------------------------------------------- /src/mcp_call.h: -------------------------------------------------------------------------------- 1 | /* 2 | * twemperf - a tool for measuring memcached server performance. 3 | * Copyright (C) 2011 Twitter, Inc. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef _MCP_CALL_H_ 20 | #define _MCP_CALL_H_ 21 | 22 | #include 23 | 24 | #define LF (uint8_t) 10 25 | #define CR (uint8_t) 13 26 | #define CRLF "\x0d\x0a" 27 | #define CRLF_LEN sizeof(CRLF) - 1 28 | 29 | #define REQ_CODEC(ACTION) \ 30 | ACTION( GET, "get " )\ 31 | ACTION( GETS, "gets " )\ 32 | ACTION( DELETE, "delete " )\ 33 | ACTION( CAS, "cas " )\ 34 | ACTION( SET, "set " )\ 35 | ACTION( ADD, "add " )\ 36 | ACTION( REPLACE, "replace " )\ 37 | ACTION( APPEND, "append " )\ 38 | ACTION( PREPEND, "prepend " )\ 39 | ACTION( INCR, "incr " )\ 40 | ACTION( DECR, "decr " )\ 41 | ACTION( XXX, "xxx " )\ 42 | 43 | #define RSP_CODEC(ACTION) \ 44 | ACTION( STORED, "STORED" )\ 45 | ACTION( NOT_STORED, "NOT_STORED" )\ 46 | ACTION( EXISTS, "EXISTS" )\ 47 | ACTION( NOT_FOUND, "NOT_FOUND" )\ 48 | ACTION( END, "END" )\ 49 | ACTION( VALUE, "VALUE" )\ 50 | ACTION( DELETED, "DELETED" )\ 51 | ACTION( ERROR, "ERROR" )\ 52 | ACTION( CLIENT_ERROR, "CLIENT_ERROR" )\ 53 | ACTION( SERVER_ERROR, "SERVER_ERROR" )\ 54 | ACTION( NUM, "" )\ 55 | 56 | #define MSG_CODEC(ACTION) \ 57 | ACTION( NOREPLY, "noreply" )\ 58 | ACTION( CRLF, "\r\n" )\ 59 | ACTION( ZERO, "0 " )\ 60 | 61 | #define DEFINE_ACTION(_type, _name) REQ_##_type, 62 | typedef enum req_type { 63 | REQ_CODEC( DEFINE_ACTION ) 64 | REQ_MAX_TYPES 65 | } req_type_t; 66 | #undef DEFINE_ACTION 67 | 68 | #define DEFINE_ACTION(_type, _name) RSP_##_type, 69 | typedef enum rsp_type { 70 | RSP_CODEC( DEFINE_ACTION ) 71 | RSP_MAX_TYPES 72 | } rsp_type_t; 73 | #undef DEFINE_ACTION 74 | 75 | #define DEFINE_ACTION(_type, _name) MSG_##_type, 76 | typedef enum msg_type { 77 | REQ_CODEC( DEFINE_ACTION ) 78 | RSP_CODEC( DEFINE_ACTION ) 79 | MSG_CODEC( DEFINE_ACTION ) 80 | MSG_MAX_TYPES 81 | } msg_type_t; 82 | #undef DEFINE_ACTION 83 | 84 | typedef enum req_iov { 85 | REQ_IOV_METHOD, 86 | REQ_IOV_KEY, 87 | REQ_IOV_FLAG, 88 | REQ_IOV_EXPIRY, 89 | REQ_IOV_VLEN, 90 | REQ_IOV_CAS, 91 | REQ_IOV_NOREPLY, 92 | REQ_IOV_CRLF, 93 | REQ_IOV_VALUE, 94 | REQ_IOV_CRLF2, 95 | REQ_IOV_LEN 96 | } req_iov_t; 97 | 98 | #define UINT32_MAX_LEN 10 99 | 100 | #define CALL_PREFIX_LEN 16 101 | #define CALL_ID_LEN 8 102 | #define CALL_KEYNAME_LEN (CALL_PREFIX_LEN + CALL_ID_LEN) 103 | #define CALL_EXPIRY_LEN UINT32_MAX_LEN 104 | #define CALL_KEYLEN_LEN UINT32_MAX_LEN 105 | 106 | /* 107 | * A call is the basic unit representing a single request followed by 108 | * a response. A call is tied to a single connection and a given 109 | * connection can have multiple outstanding calls on it. 110 | */ 111 | struct call { 112 | STAILQ_ENTRY(call) call_tqe; /* link in send / recv / free q */ 113 | uint64_t id; /* unique id */ 114 | struct conn *conn; /* owner connection */ 115 | 116 | struct { 117 | char keyname[CALL_KEYNAME_LEN]; /* key name */ 118 | char expiry[CALL_EXPIRY_LEN]; /* expiry in ascii */ 119 | char keylen[CALL_KEYLEN_LEN]; /* key length in ascii */ 120 | size_t send; /* bytes to send */ 121 | size_t sent; /* bytes sent */ 122 | double issue_start; /* issue start time in sec */ 123 | double send_start; /* send start time in sec */ 124 | double send_stop; /* send stop time in sec */ 125 | struct iovec iov[REQ_IOV_LEN]; /* request iov */ 126 | unsigned noreply:1; /* noreply? */ 127 | unsigned sending:1; /* sending call? */ 128 | } req; /* request */ 129 | 130 | struct { 131 | double recv_start; /* recv start time in sec */ 132 | size_t rcvd; /* bytes received */ 133 | char *rcurr; /* recv marker */ 134 | size_t rsize; /* recv buffer size */ 135 | char *pcurr; /* parsing marker */ 136 | char *start; /* start marker */ 137 | char *end; /* end marker */ 138 | rsp_type_t type; /* parsed response type? */ 139 | uint32_t vlen; /* value length + crlf length */ 140 | unsigned parsed_line:1; /* parsed line? */ 141 | unsigned parsed_vlen:1; /* parsed vlen? */ 142 | } rsp; /* response */ 143 | }; 144 | 145 | STAILQ_HEAD(call_tqh, call); 146 | 147 | struct call *call_get(struct conn *conn); 148 | void call_put(struct call *call); 149 | 150 | ssize_t call_sendv(struct call *call, struct iovec *iov, int iovcnt); 151 | void call_make_req(struct context *ctx, struct call *call); 152 | 153 | rstatus_t call_send(struct context *ctx, struct call *call); 154 | rstatus_t call_recv(struct context *ctx, struct call *call); 155 | 156 | void call_init(void); 157 | void call_deinit(void); 158 | 159 | #endif 160 | -------------------------------------------------------------------------------- /src/mcp_log.c: -------------------------------------------------------------------------------- 1 | /* 2 | * twemperf - a tool for measuring memcached server performance. 3 | * Copyright (C) 2011 Twitter, Inc. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include 29 | #include 30 | 31 | static struct logger logger; 32 | 33 | int 34 | log_init(int level, char *name) 35 | { 36 | struct logger *l = &logger; 37 | 38 | l->level = MAX(LOG_EMERG, MIN(level, LOG_PVERB)); 39 | l->name = name; 40 | if (name == NULL || !strlen(name)) { 41 | l->fd = STDERR_FILENO; 42 | } else { 43 | l->fd = open(name, O_WRONLY | O_APPEND | O_CREAT, 0644); 44 | if (l->fd < 0) { 45 | log_stderr("opening log file '%s' failed: %s", name, 46 | strerror(errno)); 47 | return -1; 48 | } 49 | } 50 | 51 | return 0; 52 | } 53 | 54 | void 55 | log_deinit(void) 56 | { 57 | struct logger *l = &logger; 58 | 59 | if (l->fd != STDERR_FILENO) { 60 | close(l->fd); 61 | } 62 | } 63 | 64 | void 65 | log_reopen(void) 66 | { 67 | struct logger *l = &logger; 68 | 69 | if (l->fd != STDERR_FILENO) { 70 | close(l->fd); 71 | l->fd = open(l->name, O_WRONLY | O_APPEND | O_CREAT, 0644); 72 | if (l->fd < 0) { 73 | log_stderr("reopening log file '%s'failed, ignored: %s", l->name, 74 | strerror(errno)); 75 | } 76 | } 77 | } 78 | 79 | void 80 | log_level_up(void) 81 | { 82 | struct logger *l = &logger; 83 | 84 | if (l->level < LOG_PVERB) { 85 | l->level++; 86 | loga("up log level to %d", l->level); 87 | } 88 | } 89 | 90 | void 91 | log_level_down(void) 92 | { 93 | struct logger *l = &logger; 94 | 95 | if (l->level > LOG_EMERG) { 96 | l->level--; 97 | loga("down log level to %d", l->level); 98 | } 99 | } 100 | 101 | void 102 | log_level_set(int level) 103 | { 104 | struct logger *l = &logger; 105 | 106 | l->level = MAX(LOG_EMERG, MIN(level, LOG_PVERB)); 107 | loga("set log level to %d", l->level); 108 | } 109 | 110 | int 111 | log_loggable(int level) 112 | { 113 | struct logger *l = &logger; 114 | 115 | if (level > l->level) { 116 | return 0; 117 | } 118 | 119 | return 1; 120 | } 121 | 122 | void 123 | _log(const char *file, int line, int panic, const char *fmt, ...) 124 | { 125 | struct logger *l = &logger; 126 | int len, size, errno_save; 127 | char buf[LOG_MAX_LEN], *timestr; 128 | va_list args; 129 | struct tm *local; 130 | time_t t; 131 | ssize_t n; 132 | 133 | if (l->fd < 0) { 134 | return; 135 | } 136 | 137 | errno_save = errno; 138 | len = 0; /* length of output buffer */ 139 | size = LOG_MAX_LEN; /* size of output buffer */ 140 | 141 | t = time(NULL); 142 | local = localtime(&t); 143 | timestr = asctime(local); 144 | 145 | len += mcp_scnprintf(buf + len, size - len, "[%.*s] %s:%d ", 146 | strlen(timestr) - 1, timestr, file, line); 147 | 148 | va_start(args, fmt); 149 | len += mcp_vscnprintf(buf + len, size - len, fmt, args); 150 | va_end(args); 151 | 152 | buf[len++] = '\n'; 153 | 154 | n = mcp_write(l->fd, buf, len); 155 | if (n < 0) { 156 | l->nerror++; 157 | } 158 | 159 | errno = errno_save; 160 | 161 | if (panic) { 162 | abort(); 163 | } 164 | } 165 | 166 | void 167 | _log_stderr(const char *fmt, ...) 168 | { 169 | struct logger *l = &logger; 170 | int len, size, errno_save; 171 | char buf[4 * LOG_MAX_LEN]; 172 | va_list args; 173 | ssize_t n; 174 | 175 | errno_save = errno; 176 | len = 0; /* length of output buffer */ 177 | size = 4 * LOG_MAX_LEN; /* size of output buffer */ 178 | 179 | va_start(args, fmt); 180 | len += mcp_vscnprintf(buf, size, fmt, args); 181 | va_end(args); 182 | 183 | buf[len++] = '\n'; 184 | 185 | n = mcp_write(STDERR_FILENO, buf, len); 186 | if (n < 0) { 187 | l->nerror++; 188 | } 189 | 190 | errno = errno_save; 191 | } 192 | 193 | /* 194 | * Hexadecimal dump in the canonical hex + ascii display 195 | * See -C option in man hexdump 196 | */ 197 | void 198 | _log_hexdump(const char *file, int line, char *data, int datalen, 199 | const char *fmt, ...) 200 | { 201 | struct logger *l = &logger; 202 | char buf[8 * LOG_MAX_LEN]; 203 | int i, off, len, size, errno_save; 204 | va_list args; 205 | ssize_t n; 206 | 207 | if (l->fd < 0) { 208 | return; 209 | } 210 | 211 | /* log format */ 212 | va_start(args, fmt); 213 | _log(file, line, 0, fmt); 214 | va_end(args); 215 | 216 | /* log hexdump */ 217 | errno_save = errno; 218 | off = 0; /* data offset */ 219 | len = 0; /* length of output buffer */ 220 | size = 8 * LOG_MAX_LEN; /* size of output buffer */ 221 | 222 | while (datalen != 0 && (len < size - 1)) { 223 | char *save, *str; 224 | unsigned char c; 225 | int savelen; 226 | 227 | len += mcp_scnprintf(buf + len, size - len, "%08x ", off); 228 | 229 | save = data; 230 | savelen = datalen; 231 | 232 | for (i = 0; datalen != 0 && i < 16; data++, datalen--, i++) { 233 | c = (unsigned char)(*data); 234 | str = (i == 7) ? " " : " "; 235 | len += mcp_scnprintf(buf + len, size - len, "%02x%s", c, str); 236 | } 237 | for ( ; i < 16; i++) { 238 | str = (i == 7) ? " " : " "; 239 | len += mcp_scnprintf(buf + len, size - len, " %s", str); 240 | } 241 | 242 | data = save; 243 | datalen = savelen; 244 | 245 | len += mcp_scnprintf(buf + len, size - len, " |"); 246 | 247 | for (i = 0; datalen != 0 && i < 16; data++, datalen--, i++) { 248 | c = (unsigned char)(isprint(*data) ? *data : '.'); 249 | len += mcp_scnprintf(buf + len, size - len, "%c", c); 250 | } 251 | len += mcp_scnprintf(buf + len, size - len, "|\n"); 252 | 253 | off += 16; 254 | } 255 | 256 | n = mcp_write(l->fd, buf, len); 257 | if (n < 0) { 258 | l->nerror++; 259 | } 260 | 261 | errno = errno_save; 262 | } 263 | -------------------------------------------------------------------------------- /src/mcp_core.h: -------------------------------------------------------------------------------- 1 | /* 2 | * twemperf - a tool for measuring memcached server performance. 3 | * Copyright (C) 2011 Twitter, Inc. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef _MCP_CORE_H_ 20 | #define _MCP_CORE_H_ 21 | 22 | #ifdef HAVE_CONFIG_H 23 | # include 24 | #endif 25 | 26 | #ifdef HAVE_DEBUG_LOG 27 | # define MCP_DEBUG_LOG 1 28 | #endif 29 | 30 | #ifdef HAVE_ASSERT_PANIC 31 | # define MCP_ASSERT_PANIC 1 32 | #endif 33 | 34 | #define MCP_OK 0 35 | #define MCP_ERROR -1 36 | #define MCP_EAGAIN -2 37 | #define MCP_ENOMEM -3 38 | 39 | typedef int rstatus_t; /* return type */ 40 | typedef int err_t; /* error type */ 41 | 42 | struct context; 43 | struct conn; 44 | struct call; 45 | struct epoll_event; 46 | struct string; 47 | 48 | typedef enum event_type { 49 | EVENT_INVALID = 0, 50 | 51 | EVENT_CONN_CREATED = 1, /* connection events */ 52 | EVENT_CONN_CONNECTING = 2, 53 | EVENT_CONN_CONNECTED = 3, 54 | EVENT_CONN_CLOSE = 4, 55 | EVENT_CONN_TIMEOUT = 5, 56 | EVENT_CONN_FAILED = 6, 57 | EVENT_CONN_DESTROYED = 7, 58 | 59 | EVENT_CALL_CREATED = 8, /* call events */ 60 | EVENT_CALL_ISSUE_START = 9, 61 | EVENT_CALL_SEND_START = 10, 62 | EVENT_CALL_SEND_STOP = 11, 63 | EVENT_CALL_RECV_START = 12, 64 | EVENT_CALL_RECV_STOP = 13, 65 | EVENT_CALL_DESTROYED = 14, 66 | 67 | EVENT_GEN_CONN_TRIGGER = 15, /* generator trigger and fire events */ 68 | EVENT_GEN_CONN_FIRE = 16, 69 | EVENT_GEN_CALL_TRIGGER = 17, 70 | EVENT_GEN_CALL_FIRE = 18, 71 | EVENT_GEN_SIZE_TRIGGER = 19, 72 | EVENT_GEN_SIZE_FIRE = 20, 73 | 74 | MAX_EVENT_TYPES = 21 75 | } event_type_t; 76 | 77 | #include 78 | #include 79 | #include 80 | #include 81 | #include 82 | #include 83 | #include 84 | 85 | #include 86 | #include 87 | #include 88 | #include 89 | #include 90 | #include 91 | #include 92 | #include 93 | #include 94 | #include 95 | #include 96 | 97 | struct string { 98 | char *data; /* string length */ 99 | size_t len; /* string data */ 100 | }; 101 | 102 | struct opt { 103 | int log_level; /* log level */ 104 | char *log_filename; /* log filename */ 105 | 106 | char *server; /* server name */ 107 | uint16_t port; /* server port */ 108 | struct sockinfo si; /* server socket info */ 109 | 110 | double timeout; /* connection timeout in sec */ 111 | int linger_timeout; /* linger timeout */ 112 | 113 | int send_buf_size; /* send buffer size */ 114 | int recv_buf_size; /* recv buffer size */ 115 | 116 | struct string prefix; /* key prefix */ 117 | req_type_t method; /* request type */ 118 | uint32_t expiry; /* key expiry */ 119 | 120 | struct { 121 | uint32_t id; /* unique client id */ 122 | uint32_t n; /* # client */ 123 | } client; /* client */ 124 | 125 | uint32_t num_conns; /* # connections */ 126 | uint32_t num_calls; /* # calls */ 127 | 128 | struct dist_opt conn_dopt; /* conn distribution option */ 129 | struct dist_opt call_dopt; /* call distribution option */ 130 | struct dist_opt size_dopt; /* size distribution option */ 131 | 132 | unsigned print_histogram:1; /* print response time histogram? */ 133 | unsigned disable_nodelay:1; /* disable_nodelay? */ 134 | unsigned print_rusage:1; /* print rusage? */ 135 | unsigned linger:1; /* linger? */ 136 | unsigned use_noreply:1; /* use_noreply? */ 137 | }; 138 | 139 | struct context { 140 | struct opt opt; /* cmdline option */ 141 | 142 | int ep; /* epoll descriptor */ 143 | struct epoll_event *event; /* epoll event */ 144 | int nevent; /* # epoll event */ 145 | int timeout; /* epoll timeout */ 146 | 147 | uint32_t nconn_created; /* # connection created */ 148 | uint32_t nconn_create_failed; /* # connection create failed */ 149 | uint32_t nconn_destroyed; /* # connection destroyed */ 150 | 151 | struct dist_info conn_dist; /* conn generator distribution */ 152 | struct dist_info call_dist; /* call generator distribution */ 153 | struct dist_info size_dist; /* size generator distribution */ 154 | 155 | struct gen conn_gen; /* connection generator */ 156 | struct gen size_gen; /* size generator */ 157 | 158 | struct action action[MAX_EVENT_TYPES]; /* event actions */ 159 | 160 | struct stats stats; /* statistics */ 161 | 162 | char buf1m[MB]; /* 1M buffer */ 163 | }; 164 | 165 | typedef void (*init_t)(struct context *, void *); 166 | typedef void (*deinit_t)(struct context *, void *); 167 | typedef void (*dump_t)(struct context *, void *); 168 | typedef void (*start_t)(struct context *, void *); 169 | typedef void (*stop_t)(struct context *, void *); 170 | 171 | struct load_generator { 172 | char *name; /* generator name */ 173 | init_t init; /* init generator */ 174 | deinit_t deinit; /* deinit generator */ 175 | start_t start; /* start generator */ 176 | stop_t stop; /* stop generator */ 177 | }; 178 | 179 | struct stats_collector { 180 | char *name; /* collector name */ 181 | init_t init; /* init collector */ 182 | start_t start; /* start collector */ 183 | stop_t stop; /* stop collector */ 184 | dump_t dump; /* dump collector */ 185 | }; 186 | 187 | rstatus_t core_init(struct context *ctx); 188 | void core_deinit(struct context *ctx); 189 | 190 | void core_start(struct context *ctx); 191 | void core_stop(struct context *ctx); 192 | rstatus_t core_loop(struct context *ctx); 193 | 194 | rstatus_t core_connect(struct context *ctx, struct conn *conn); 195 | void core_send(struct context *ctx, struct conn *conn); 196 | void core_recv(struct context *ctx, struct conn *conn); 197 | void core_close(struct context *ctx, struct conn *conn); 198 | void core_error(struct context *ctx, struct conn *conn); 199 | 200 | void core_timeout(struct timer *t, void *arg); 201 | 202 | #endif 203 | -------------------------------------------------------------------------------- /src/mcp_util.h: -------------------------------------------------------------------------------- 1 | /* 2 | * twemperf - a tool for measuring memcached server performance. 3 | * Copyright (C) 2011 Twitter, Inc. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #ifndef _MCP_UTIL_H_ 20 | #define _MCP_UTIL_H_ 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include 28 | #include 29 | #include 30 | #include 31 | 32 | #define SQUARE(d) ((d) * (d)) 33 | #define VAR(s, s2, n) \ 34 | (((n) < 2) ? 0.0 : ((s2) - SQUARE(s) / (n)) / ((n) - 1)) 35 | #define STDDEV(s, s2, n) (((n) < 2) ? 0.0 : sqrt(VAR((s), (s2), (n)))) 36 | 37 | #define MIN(a, b) ((a) < (b) ? (a) : (b)) 38 | #define MAX(a, b) ((a) > (b) ? (a) : (b)) 39 | 40 | #define NELEM(a) ((sizeof(a)) / sizeof((a)[0])) 41 | 42 | #define KB (1024) 43 | #define MB (1024 * KB) 44 | #define GB (1024 * MB) 45 | 46 | /* 47 | * Length of 1 byte, 2 bytes, 4 bytes, 8 bytes and largest integral 48 | * type (uintmax_t) in ascii, including the null terminator '\0' 49 | * 50 | * From stdint.h, we have: 51 | * # define UINT8_MAX (255) 52 | * # define UINT16_MAX (65535) 53 | * # define UINT32_MAX (4294967295U) 54 | * # define UINT64_MAX (__UINT64_C(18446744073709551615)) 55 | */ 56 | #define MCP_UINT8_MAXLEN (3 + 1) 57 | #define MCP_UINT16_MAXLEN (5 + 1) 58 | #define MCP_UINT32_MAXLEN (10 + 1) 59 | #define MCP_UINT64_MAXLEN (20 + 1) 60 | #define MCP_UINTMAX_MAXLEN MCP_UINT64_MAXLEN 61 | 62 | /* timeval to seconds */ 63 | #define TV_TO_SEC(_tv) ((_tv)->tv_sec + (1e-6 * (_tv)->tv_usec)) 64 | 65 | #define MCP_INET4_ADDRSTRLEN (sizeof("255.255.255.255") - 1) 66 | #define MCP_INET6_ADDRSTRLEN \ 67 | (sizeof("ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255") - 1) 68 | #define MCP_INET_ADDRSTRLEN MAX(MCP_INET4_ADDRSTRLEN, MCP_INET6_ADDRSTRLEN) 69 | #define MCP_UNIX_ADDRSTRLEN \ 70 | (sizeof(struct sockaddr_un) - offsetof(struct sockaddr_un, sun_path)) 71 | 72 | /* 73 | * Unified socket address combining inet and unix 74 | * domain sockets 75 | */ 76 | struct sockinfo { 77 | int family; /* socket address family */ 78 | socklen_t addrlen; /* socket address length */ 79 | union { 80 | struct sockaddr_in in; /* ipv4 socket address */ 81 | struct sockaddr_in6 in6; /* ipv6 socket address */ 82 | struct sockaddr_un un; /* unix domain address */ 83 | } addr; 84 | }; 85 | 86 | int mcp_resolve_addr(char *name, int port, struct sockinfo *si); 87 | 88 | int mcp_set_nonblocking(int sd); 89 | int mcp_set_tcpnodelay(int sd); 90 | int mcp_set_linger(int sd, int timeout); 91 | int mcp_set_sndbuf(int sd, int size); 92 | int mcp_set_rcvbuf(int sd, int size); 93 | 94 | int mcp_get_soerror(int sd); 95 | int mcp_get_sndbuf(int sd); 96 | int mcp_get_rcvbuf(int sd); 97 | 98 | bool mcp_valid_port(int n); 99 | int mcp_atoi(char *line); 100 | double mcp_atod(char *line); 101 | 102 | /* 103 | * Wrapper around common routines for manipulating C character 104 | * strings 105 | */ 106 | #define mcp_memcpy(_d, _c, _n) \ 107 | memcpy(_d, _c, (size_t)(_n)) 108 | 109 | #define mcp_memmove(_d, _c, _n) \ 110 | memmove(_d, _c, (size_t)(_n)) 111 | 112 | #define mcp_memchr(_d, _c, _n) \ 113 | memchr(_d, _c, (size_t)(_n)) 114 | 115 | #define mcp_strlen(_s) \ 116 | strlen((char *)(_s)) 117 | 118 | #define mcp_strncmp(_s1, _s2, _n) \ 119 | strncmp((char *)(_s1), (char *)(_s2), (size_t)(_n)) 120 | 121 | #define mcp_strchr(_p, _l, _c) \ 122 | _mcp_strchr((uint8_t *)(_p), (uint8_t *)(_l), (uint8_t)(_c)) 123 | 124 | #define mcp_strrchr(_p, _s, _c) \ 125 | _mcp_strrchr((uint8_t *)(_p),(uint8_t *)(_s), (uint8_t)(_c)) 126 | 127 | #define mcp_strndup(_s, _n) \ 128 | (uint8_t *)strndup((char *)(_s), (size_t)(_n)); 129 | 130 | #define mcp_snprintf(_s, _n, ...) \ 131 | snprintf((char *)(_s), (size_t)(_n), __VA_ARGS__) 132 | 133 | #define mcp_scnprintf(_s, _n, ...) \ 134 | _scnprintf((char *)(_s), (size_t)(_n), __VA_ARGS__) 135 | 136 | #define mcp_vscnprintf(_s, _n, _f, _a) \ 137 | _vscnprintf((char *)(_s), (size_t)(_n), _f, _a) 138 | 139 | static inline uint8_t * 140 | _mcp_strchr(uint8_t *p, uint8_t *last, uint8_t c) 141 | { 142 | while (p < last) { 143 | if (*p == c) { 144 | return p; 145 | } 146 | p++; 147 | } 148 | 149 | return NULL; 150 | } 151 | 152 | static inline uint8_t * 153 | _mcp_strrchr(uint8_t *p, uint8_t *start, uint8_t c) 154 | { 155 | while (p >= start) { 156 | if (*p == c) { 157 | return p; 158 | } 159 | p--; 160 | } 161 | 162 | return NULL; 163 | } 164 | 165 | /* 166 | * Memory allocation and free wrappers. 167 | * 168 | * These wrappers enables us to loosely detect double free, dangling 169 | * pointer access and zero-byte alloc. 170 | */ 171 | #define mcp_alloc(_s) _mcp_alloc((size_t)(_s), __FILE__, __LINE__) 172 | #define mcp_zalloc(_s) _mcp_zalloc((size_t)(_s), __FILE__, __LINE__) 173 | #define mcp_calloc(_n, _s) _mcp_calloc((size_t)(_n), (size_t)(_s), __FILE__, __LINE__) 174 | #define mcp_realloc(_p, _s) _mcp_realloc(_p, (size_t)(_s), __FILE__, __LINE__) 175 | #define mcp_free(_p) do { \ 176 | _mcp_free(_p); \ 177 | (_p) = NULL; \ 178 | } while (0) 179 | 180 | void *_mcp_alloc(size_t size, char *name, int line); 181 | void *_mcp_zalloc(size_t size, char *name, int line); 182 | void *_mcp_calloc(size_t nmemb, size_t size, char *name, int line); 183 | void *_mcp_realloc(void *ptr, size_t size, char *name, int line); 184 | void _mcp_free(void *ptr); 185 | 186 | /* 187 | * Wrappers to send or receive n byte message on a blocking 188 | * socket descriptor. 189 | */ 190 | #define mcp_sendn(_s, _b, _n) _mcp_sendn(_s, _b, (size_t)(_n)) 191 | #define mcp_recvn(_s, _b, _n) _mcp_recvn(_s, _b, (size_t)(_n)) 192 | 193 | /* 194 | * Wrappers to read or write data to/from (multiple) buffers 195 | * to a file or socket descriptor. 196 | */ 197 | #define mcp_read(_d, _b, _n) read(_d, _b, (size_t)(_n)) 198 | #define mcp_readv(_d, _b, _n) readv(_d, _b, (int)(_n)) 199 | 200 | #define mcp_write(_d, _b, _n) write(_d, _b, (size_t)(_n)) 201 | #define mcp_writev(_d, _b, _n) writev(_d, _b, (int)(_n)) 202 | 203 | ssize_t _mcp_sendn(int sd, const void *vptr, size_t n); 204 | ssize_t _mcp_recvn(int sd, void *vptr, size_t n); 205 | 206 | void mcp_assert(const char *cond, const char *file, int line, int panic); 207 | 208 | #ifdef MCP_ASSERT_PANIC 209 | 210 | #define ASSERT(_x) do { \ 211 | if (!(_x)) { \ 212 | mcp_assert(#_x, __FILE__, __LINE__, 1); \ 213 | } \ 214 | } while (0) 215 | 216 | #define NOT_REACHED() ASSERT(0) 217 | 218 | #elif MCP_ASSERT_LOG 219 | 220 | #define ASSERT(_x) do { \ 221 | if (!(_x)) { \ 222 | mcp_assert(#_x, __FILE__, __LINE__, 0); \ 223 | } \ 224 | } while (0) 225 | 226 | #define NOT_REACHED() ASSERT(0) 227 | 228 | #else 229 | 230 | #define ASSERT(_x) 231 | 232 | #define NOT_REACHED() 233 | 234 | #endif 235 | 236 | void mcp_stacktrace(int skip_count); 237 | int _scnprintf(char *buf, size_t size, const char *fmt, ...); 238 | int _vscnprintf(char *buf, size_t size, const char *fmt, va_list args); 239 | 240 | int64_t mcp_usec_now(void); 241 | 242 | #endif 243 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # twemperf (mcperf) 2 | 3 | **twemperf** (pronounced "two-em-perf"), aka **mcperf** is a tool for 4 | measuring memcached server performance. mcperf is like httperf, but for 5 | memcached protocol. It speaks memcached ASCII protocol and is capable of 6 | generating connections and requests at a high rate. 7 | 8 | ## Building mcperf ## 9 | 10 | To build mcperf from [distribution tarball](http://code.google.com/p/twemperf/downloads/list): 11 | 12 | $ ./configure 13 | $ make 14 | $ sudo make install 15 | 16 | To build mcperf from [distribution tarball](http://code.google.com/p/twemperf/downloads/list) in _debug mode_: 17 | 18 | $ CFLAGS="-ggdb3 -O0" ./configure --enable-debug 19 | $ make 20 | $ sudo make install 21 | 22 | To build mcperf from source in _debug mode_: 23 | 24 | $ git clone git://github.com/twitter/twemperf.git 25 | $ cd twemperf 26 | $ autoreconf -fvi 27 | $ CFLAGS="-ggdb3 -O0" ./configure --enable-debug 28 | $ make 29 | $ src/mcperf -h 30 | 31 | ## Help ## 32 | 33 | Usage: mcperf [-?hV] [-v verbosity level] [-o output file] 34 | [-s server] [-p port] [-H] [-t timeout] [-l linger] 35 | [-b send-buffer] [-B recv-buffer] [-D] 36 | [-m method] [-e expiry] [-q] [-P prefix] 37 | [-c client] [-n num-conns] [-N num-calls] 38 | [-r conn-rate] [-R call-rate] [-z sizes] 39 | 40 | Options: 41 | -h, --help : this help 42 | -V, --version : show version and exit 43 | -v, --verbosity=N : set logging level (default: 5, min: 0, max: 11) 44 | -o, --output=S : set logging file (default: stderr) 45 | -s, --server=S : set the hostname of the server (default: localhost) 46 | -p, --port=N : set the port number of the server (default: 11211) 47 | -H, --print-histogram : print response time histogram 48 | ... 49 | -t, --timeout=X : set the connection and response timeout in sec (default: 0.0 sec) 50 | -l, --linger=N : set the linger timeout in sec, when closing TCP connections (default: off) 51 | -b, --send-buffer=N : set socket send buffer size (default: 4096 bytes) 52 | -B, --recv-buffer=N : set socket recv buffer size (default: 16384 bytes) 53 | -D, --disable-nodelay : disable tcp nodelay 54 | ... 55 | -m, --method=M : set the method to use when issuing memcached request (default: set) 56 | -e, --expiry=N : set the expiry value in sec for generated requests (default: 0 sec) 57 | -q, --use-noreply : set noreply for generated requests 58 | -P, --prefix=S : set the prefix of generated keys (default: mcp:) 59 | ... 60 | -c, --client=I/N : set mcperf instance to be I out of total N instances (default: 0/1) 61 | -n, --num-conns=N : set the number of connections to create (default: 1) 62 | -N, --num-calls=N : set the number of calls to create on each connection (default: 1) 63 | -r, --conn-rate=R : set the connection creation rate (default: 0 conns/sec) 64 | -R, --call-rate=R : set the call creation rate (default: 0 calls/sec) 65 | -z, --sizes=R : set the distribution for item sizes (default: d1 bytes) 66 | ... 67 | Where: 68 | N is an integer 69 | X is a real 70 | S is a string 71 | M is a method string and is either a 'get', 'gets', 'delete', 'cas', 'set', 'add', 'replace' 72 | 'append', 'prepend', 'incr', 'decr' 73 | R is the rate written as [D]R1[,R2] where: 74 | D is the distribution type and is either deterministic 'd', uniform 'u', or exponential 'e' and if: 75 | D is ommited or set to 'd', a deterministic interval specified by parameter R1 is used 76 | D is set to 'e', an exponential distibution with mean interval of R1 is used 77 | D is set to 'u', a uniform distribution over interval [R1, R2) is used 78 | R is 0, the next request or connection is created after the previous one completes 79 | 80 | ## Design ## 81 | 82 | 1. Single threaded. 83 | 2. Asynchronous I/O through non-blocking sockets and Linux epoll(7) syscall. 84 | 3. Horizonal scaling through many concurrent mcperf processes on several 85 | different machines. 86 | 87 | mcperf is composed of three main subsystems: 88 | 89 | 1. Core Engine 90 | 2. Load Generator 91 | 3. Stats Collector 92 | 93 | The core engine is responsible for the event handling and parsing of 94 | memcached protocol and drives the main event loop. 95 | 96 | The load generator is resposible for generating load either periodically 97 | or as a one-shot event. Each load generator is implemented as a self 98 | contained module. Some examples of load generators are: 99 | 100 | 1. Connection generator creates connections to a server at a given rate. 101 | 2. Call generator issue calls (requests) on a connection at a given rate. 102 | 3. Size generator generates item sizes. 103 | 104 | The stats collector is responsible for collecting statistics. Each stats 105 | collector is implemented as a self contained module. Some examples of 106 | stats collectors are: 107 | 108 | 1. Connection stats collects connection stats. 109 | 2. Call stats collects call (request and response) stats. 110 | 111 | ## Examples ## 112 | 113 | The following example creates **1000 connections** to a memcached server 114 | running on **localhost:11211**. The connections are created at the rate of 115 | **1000 conns/sec** and on every connection it sends **10 'set' requests** at 116 | the rate of **1000 reqs/sec** with the item sizes derived from a uniform 117 | distribution in the interval of [1,16) bytes. 118 | 119 | $ mcperf --linger=0 --timeout=5 --conn-rate=1000 --call-rate=1000 --num-calls=10 --num-conns=1000 --sizes=u1,16 120 | 121 | Total: connections 1000 requests 10000 responses 10000 test-duration 1.009 s 122 | 123 | Connection rate: 991.1 conn/s (1.0 ms/conn <= 23 concurrent connections) 124 | Connection time [ms]: avg 10.3 min 10.1 max 14.1 stddev 0.1 125 | Connect time [ms]: avg 0.2 min 0.1 max 0.8 stddev 0.0 126 | 127 | Request rate: 9910.5 req/s (0.1 ms/req) 128 | Request size [B]: avg 35.9 min 28.0 max 44.0 stddev 4.8 129 | 130 | Response rate: 9910.5 rsp/s (0.1 ms/rsp) 131 | Response size [B]: avg 8.0 min 8.0 max 8.0 stddev 0.0 132 | Response time [ms]: avg 0.2 min 0.1 max 13.4 stddev 0.00 133 | Response time [ms]: p25 1.0 p50 1.0 p75 1.0 134 | Response time [ms]: p95 1.0 p99 1.0 p999 1.0 135 | Response type: stored 10000 not_stored 0 exists 0 not_found 0 136 | Response type: num 0 deleted 0 end 0 value 0 137 | Response type: error 0 client_error 0 server_error 0 138 | 139 | Errors: total 0 client-timo 0 socket-timo 0 connrefused 0 connreset 0 140 | Errors: fd-unavail 0 ftab-full 0 addrunavail 0 other 0 141 | 142 | CPU time [s]: user 0.64 system 0.35 (user 63.6% system 35.1% total 98.7%) 143 | Net I/O: bytes 428.7 KB rate 424.8 KB/s (3.5*10^6 bps) 144 | 145 | The following example creates **100 connections** to a memcached server 146 | running on **localhost:11211**. Every connection is created after the previous 147 | connection is closed. On every connection we send **100 'set' requests** and 148 | every request is created after we have received the response for the previous 149 | request. All the set requests generated have a fixed item size of 1 byte. 150 | 151 | $ mcperf --linger=0 --call-rate=0 --num-calls=100 --conn-rate=0 --num-conns=100 --sizes=d1 152 | 153 | Total: connections 100 requests 10000 responses 10000 test-duration 1.268 s 154 | 155 | Connection rate: 78.9 conn/s (12.7 ms/conn <= 1 concurrent connections) 156 | Connection time [ms]: avg 12.7 min 12.6 max 13.5 stddev 0.1 157 | Connect time [ms]: avg 0.0 min 0.0 max 0.1 stddev 0.0 158 | 159 | Request rate: 7886.1 req/s (0.1 ms/req) 160 | Request size [B]: avg 28.0 min 28.0 max 28.0 stddev 0.0 161 | 162 | Response rate: 7886.1 rsp/s (0.1 ms/rsp) 163 | Response size [B]: avg 8.0 min 8.0 max 8.0 stddev 0.0 164 | Response time [ms]: avg 0.1 min 0.1 max 1.0 stddev 0.00 165 | Response time [ms]: p25 1.0 p50 1.0 p75 1.0 166 | Response time [ms]: p95 1.0 p99 1.0 p999 1.0 167 | Response type: stored 10000 not_stored 0 exists 0 not_found 0 168 | Response type: num 0 deleted 0 end 0 value 0 169 | Response type: error 0 client_error 0 server_error 0 170 | 171 | Errors: total 0 client-timo 0 socket-timo 0 connrefused 0 connreset 0 172 | Errors: fd-unavail 0 ftab-full 0 addrunavail 0 other 0 173 | 174 | CPU time [s]: user 0.51 system 0.75 (user 40.0% system 59.0% total 99.0%) 175 | Net I/O: bytes 351.6 KB rate 277.2 KB/s (2.3*10^6 bps) 176 | 177 | ## Issues and Support ## 178 | 179 | Have a bug or a question? Please create an issue here on GitHub! 180 | 181 | https://github.com/twitter/twemperf/issues 182 | 183 | ## Contributors ## 184 | 185 | * Manju Rajashekhar (@manju) 186 | * Clojure Janet (@clojurejanet) 187 | -------------------------------------------------------------------------------- /src/mcp_core.c: -------------------------------------------------------------------------------- 1 | /* 2 | * twemperf - a tool for measuring memcached server performance. 3 | * Copyright (C) 2011 Twitter, Inc. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include 20 | 21 | #include 22 | 23 | extern struct load_generator size_generator, conn_generator, call_generator; 24 | extern struct stats_collector conn_stats, call_stats; 25 | 26 | static struct load_generator *gen[] = { /* load generators */ 27 | &size_generator, 28 | &conn_generator, 29 | &call_generator 30 | }; 31 | 32 | static struct stats_collector *col[] = { /* stats collectors */ 33 | &conn_stats, 34 | &call_stats 35 | }; 36 | 37 | rstatus_t 38 | core_init(struct context *ctx) 39 | { 40 | rstatus_t status; 41 | struct opt *opt = &ctx->opt; 42 | uint32_t i; 43 | 44 | /* initialize event machine */ 45 | ctx->timeout = TIMER_INTERVAL * 1e3; 46 | ctx->nevent = (int)opt->num_conns; 47 | status = event_init(ctx, EVENT_SIZE_HINT); 48 | if (status != MCP_OK) { 49 | return status; 50 | } 51 | 52 | /* initialize connection subsystem */ 53 | conn_init(); 54 | 55 | /* initialize call subsystem */ 56 | call_init(); 57 | 58 | /* initialize the stats collectors before the load generators */ 59 | for (i = 0; i < NELEM(col); i++) { 60 | col[i]->init(ctx, NULL); 61 | } 62 | 63 | /* initialize the load generators */ 64 | for (i = 0; i < NELEM(gen); i++) { 65 | gen[i]->init(ctx, NULL); 66 | } 67 | 68 | return MCP_OK; 69 | } 70 | 71 | void 72 | core_deinit(struct context *ctx) 73 | { 74 | } 75 | 76 | void 77 | core_start(struct context *ctx) 78 | { 79 | uint32_t i; 80 | 81 | /* start the stats subsystem */ 82 | stats_start(ctx); 83 | 84 | /* start stats collectors */ 85 | for (i = 0; i < NELEM(col); i++) { 86 | col[i]->start(ctx, NULL); 87 | } 88 | 89 | /* 90 | * Before the connection generator is triggered, all its dependent 91 | * generators must be triggered. 92 | */ 93 | ecb_signal(ctx, EVENT_GEN_SIZE_TRIGGER, NULL); 94 | 95 | /* start the connection generator by triggering it */ 96 | ecb_signal(ctx, EVENT_GEN_CONN_TRIGGER, NULL); 97 | } 98 | 99 | void 100 | core_stop(struct context *ctx) 101 | { 102 | mcp_free(ctx->event); 103 | close(ctx->ep); 104 | 105 | stats_dump(ctx); 106 | } 107 | 108 | void 109 | core_timeout(struct timer *t, void *arg) 110 | { 111 | struct conn *conn = arg; 112 | struct context *ctx = conn->ctx; 113 | 114 | log_debug(LOG_INFO, "c %"PRIu64" on sd %d timedout", conn->id, conn->sd); 115 | 116 | /* timer are freed by the timeout handler */ 117 | ASSERT(conn->watchdog == t); 118 | conn->watchdog = NULL; 119 | 120 | conn->connecting = 0; 121 | 122 | ecb_signal(ctx, EVENT_CONN_TIMEOUT, conn); 123 | ecb_signal(ctx, EVENT_CONN_DESTROYED, conn); 124 | } 125 | 126 | static rstatus_t 127 | core_connecting(struct context *ctx, struct conn *conn) 128 | { 129 | struct opt *opt = &ctx->opt; 130 | 131 | ASSERT(!conn->connecting); 132 | ASSERT(conn->watchdog == NULL); 133 | 134 | if (opt->timeout > 0.0) { 135 | conn->watchdog = timer_schedule(core_timeout, conn, opt->timeout); 136 | if (conn->watchdog == NULL) { 137 | return MCP_ENOMEM; 138 | } 139 | } 140 | 141 | conn->connecting = 1; 142 | 143 | log_debug(LOG_VERB, "connecting on c %"PRIu64" sd %d", conn->id, conn->sd); 144 | 145 | return MCP_OK; 146 | } 147 | 148 | static void 149 | core_connected(struct context *ctx, struct conn *conn) 150 | { 151 | struct opt *opt = &ctx->opt; 152 | 153 | ASSERT(conn->connecting); 154 | ASSERT(!conn->connected); 155 | 156 | log_debug(LOG_DEBUG, "connected on c %"PRIu64" sd %d", conn->id, conn->sd); 157 | 158 | conn->connecting = 0; 159 | conn->connected = 1; 160 | 161 | if (opt->timeout > 0.0) { 162 | ASSERT(conn->watchdog != NULL); 163 | timer_cancel(conn->watchdog); 164 | } 165 | 166 | ecb_signal(ctx, EVENT_CONN_CONNECTED, conn); 167 | 168 | /* 169 | * Before a call generator is triggered, we must have a 170 | * connected connection. 171 | */ 172 | ecb_signal(ctx, EVENT_GEN_CALL_TRIGGER, conn); 173 | } 174 | 175 | rstatus_t 176 | core_connect(struct context *ctx, struct conn *conn) 177 | { 178 | rstatus_t status; 179 | struct opt *opt = &ctx->opt; 180 | struct sockinfo *si = &opt->si; 181 | 182 | ASSERT(conn->sd < 0); 183 | 184 | conn->sd = socket(si->family, SOCK_STREAM, 0); 185 | if (conn->sd < 0) { 186 | log_debug(LOG_ERR, "socket create for c %"PRIu64" failed: %s", 187 | conn->id, strerror(errno)); 188 | status = MCP_ERROR; 189 | goto error; 190 | } 191 | 192 | status = mcp_set_nonblocking(conn->sd); 193 | if (status != MCP_OK) { 194 | log_debug(LOG_ERR, "set nonblock on c %"PRIu64" sd %d failed: %s", 195 | conn->id, conn->sd, strerror(errno)); 196 | goto error; 197 | } 198 | 199 | if (!opt->disable_nodelay) { 200 | status = mcp_set_tcpnodelay(conn->sd); 201 | if (status != MCP_OK) { 202 | log_debug(LOG_ERR, "set tcpnodelay on c %"PRIu64" sd %d failed: %s", 203 | conn->id, conn->sd, strerror(errno)); 204 | goto error; 205 | } 206 | } 207 | 208 | if (opt->linger) { 209 | status = mcp_set_linger(conn->sd, opt->linger_timeout); 210 | if (status != MCP_OK) { 211 | log_debug(LOG_ERR, "set linger on c %"PRIu64" sd %d failed: %s", 212 | conn->id, conn->sd, strerror(errno)); 213 | goto error; 214 | } 215 | } 216 | 217 | status = mcp_set_sndbuf(conn->sd, opt->send_buf_size); 218 | if (status != MCP_OK) { 219 | log_debug(LOG_ERR, "set sndbuf on c %"PRIu64" sd %d to %d failed: %s", 220 | conn->id, conn->sd, opt->send_buf_size); 221 | goto error; 222 | } 223 | 224 | status = mcp_set_rcvbuf(conn->sd, opt->recv_buf_size); 225 | if (status != MCP_OK) { 226 | log_debug(LOG_ERR, "set rcvbuf on c %"PRIu64" sd %d to %d failed: %s", 227 | conn->id, conn->sd, opt->recv_buf_size); 228 | goto error; 229 | } 230 | 231 | status = event_add_conn(ctx->ep, conn); 232 | if (status != MCP_OK) { 233 | log_debug(LOG_ERR, "event add conn e %d sd %d failed: %s", ctx->ep, 234 | conn->sd, strerror(errno)); 235 | goto error; 236 | } 237 | 238 | ecb_signal(ctx, EVENT_CONN_CONNECTING, conn); 239 | 240 | status = connect(conn->sd, (struct sockaddr *)&si->addr, si->addrlen); 241 | if (status != MCP_OK) { 242 | if (errno == EINPROGRESS) { 243 | status = core_connecting(ctx, conn); 244 | if (status == MCP_OK) { 245 | return MCP_OK; 246 | } 247 | } 248 | log_debug(LOG_ERR, "connect on c %"PRIu64" sd %d failed: %s", conn->id, 249 | conn->sd, strerror(errno)); 250 | goto error; 251 | } 252 | 253 | ASSERT(!conn->connecting); 254 | ASSERT(!conn->connected); 255 | ASSERT(conn->watchdog == NULL); 256 | 257 | conn->connected = 1; 258 | 259 | log_debug(LOG_INFO, "connected on c %"PRIu64" sd %d", conn->id, conn->sd); 260 | 261 | ecb_signal(ctx, EVENT_CONN_CONNECTED, conn); 262 | 263 | /* 264 | * Before a call generator is triggered, we must have a 265 | * connected connection. 266 | */ 267 | ecb_signal(ctx, EVENT_GEN_CALL_TRIGGER, conn); 268 | 269 | return MCP_OK; 270 | 271 | error: 272 | conn->err = errno; 273 | return status; 274 | } 275 | 276 | void 277 | core_send(struct context *ctx, struct conn *conn) 278 | { 279 | rstatus_t status; 280 | struct call *call; 281 | 282 | if (conn->connecting) { 283 | core_connected(ctx, conn); 284 | } 285 | 286 | conn->send_ready = 1; 287 | do { 288 | call = STAILQ_FIRST(&conn->call_sendq); 289 | if (call == NULL) { 290 | return; 291 | } 292 | 293 | status = call_send(ctx, call); 294 | if (status != MCP_OK) { 295 | return; 296 | } 297 | 298 | } while (conn->send_ready); 299 | } 300 | 301 | void 302 | core_recv(struct context *ctx, struct conn *conn) 303 | { 304 | rstatus_t status; 305 | struct call *call; 306 | 307 | ASSERT(!conn->connecting); 308 | 309 | conn->recv_ready = 1; 310 | do { 311 | call = STAILQ_FIRST(&conn->call_recvq); 312 | if (call == NULL) { 313 | return; 314 | } 315 | 316 | status = call_recv(ctx, call); 317 | if (status != MCP_OK) { 318 | return; 319 | } 320 | } while (conn->recv_ready); 321 | } 322 | 323 | void 324 | core_close(struct context *ctx, struct conn *conn) 325 | { 326 | rstatus_t status; 327 | struct call *call, *ncall; /* current and next call */ 328 | 329 | if (conn->sd < 0) { 330 | return; 331 | } 332 | 333 | for (call = STAILQ_FIRST(&conn->call_recvq); call != NULL; call = ncall) { 334 | ncall = STAILQ_NEXT(call, call_tqe); 335 | 336 | ASSERT(conn->ncall_recvq != 0); 337 | 338 | conn->ncall_recvq--; 339 | STAILQ_REMOVE(&conn->call_recvq, call, call, call_tqe); 340 | call_put(call); 341 | } 342 | ASSERT(conn->ncall_recvq == 0); 343 | 344 | for (call = STAILQ_FIRST(&conn->call_sendq); call != NULL; call = ncall) { 345 | ncall = STAILQ_NEXT(call, call_tqe); 346 | 347 | ASSERT(conn->ncall_sendq != 0); 348 | 349 | conn->ncall_sendq--; 350 | STAILQ_REMOVE(&conn->call_sendq, call, call, call_tqe); 351 | call_put(call); 352 | } 353 | ASSERT(conn->ncall_sendq == 0); 354 | 355 | status = close(conn->sd); 356 | if (status != MCP_OK) { 357 | log_debug(LOG_ERR, "close c %"PRIu64" sd %d failed: %s", conn->id, 358 | conn->sd, strerror(errno)); 359 | } 360 | conn->sd = -1; 361 | 362 | conn_put(conn); 363 | } 364 | 365 | void 366 | core_error(struct context *ctx, struct conn *conn) 367 | { 368 | rstatus_t status; 369 | 370 | if (conn->err == 0) { 371 | status = mcp_get_soerror(conn->sd); 372 | if (status < 0) { 373 | log_debug(LOG_ERR, "get soerr on c %"PRIu64" sd %d failed: %s", 374 | conn->id, conn->sd, strerror(errno)); 375 | } 376 | conn->err = errno; 377 | } 378 | 379 | log_debug(LOG_ERR, "error on c %"PRIu64" sd %d: %s", conn->id, conn->sd, 380 | strerror(conn->err)); 381 | 382 | ecb_signal(ctx, EVENT_CONN_FAILED, conn); 383 | ecb_signal(ctx, EVENT_CONN_DESTROYED, conn); 384 | } 385 | 386 | static void 387 | core_core(struct context *ctx, struct conn *conn, uint32_t events) 388 | { 389 | if (events & EPOLLERR) { 390 | core_error(ctx, conn); 391 | return; 392 | } 393 | 394 | /* read takes precedence over write */ 395 | if (events & (EPOLLIN | EPOLLHUP)) { 396 | core_recv(ctx, conn); 397 | if (conn->eof || conn->err != 0) { 398 | core_error(ctx, conn); 399 | return; 400 | } 401 | } 402 | 403 | if (events & EPOLLOUT) { 404 | core_send(ctx, conn); 405 | if (conn->err != 0) { 406 | core_error(ctx, conn); 407 | return; 408 | } 409 | } 410 | } 411 | 412 | rstatus_t 413 | core_loop(struct context *ctx) 414 | { 415 | int i, nsd; 416 | 417 | timer_tick(); 418 | 419 | nsd = event_wait(ctx->ep, ctx->event, ctx->nevent, ctx->timeout); 420 | if (nsd < 0) { 421 | return nsd; 422 | } 423 | 424 | for (i = 0; i < nsd; i++) { 425 | struct epoll_event *ev = &ctx->event[i]; 426 | 427 | core_core(ctx, ev->data.ptr, ev->events); 428 | 429 | timer_tick(); 430 | } 431 | 432 | return MCP_OK; 433 | } 434 | -------------------------------------------------------------------------------- /src/mcp_util.c: -------------------------------------------------------------------------------- 1 | /* 2 | * twemperf - a tool for measuring memcached server performance. 3 | * Copyright (C) 2011 Twitter, Inc. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | #include 35 | #include 36 | 37 | #include 38 | #include 39 | #include 40 | 41 | static int 42 | mcp_resolve_addr_inet(char *name, int port, struct sockinfo *si) 43 | { 44 | int status; 45 | struct addrinfo *ai, *cai; /* head and current addrinfo */ 46 | struct addrinfo hints; 47 | char *node, nodestr[MCP_INET_ADDRSTRLEN], service[MCP_UINTMAX_MAXLEN]; 48 | bool found; 49 | 50 | ASSERT(mcp_valid_port(port)); 51 | 52 | memset(&hints, 0, sizeof(hints)); 53 | hints.ai_flags = AI_NUMERICSERV; 54 | hints.ai_family = AF_UNSPEC; /* AF_INET or AF_INET6 */ 55 | hints.ai_socktype = SOCK_STREAM; 56 | hints.ai_protocol = 0; 57 | hints.ai_addrlen = 0; 58 | hints.ai_addr = NULL; 59 | hints.ai_canonname = NULL; 60 | 61 | if (name != NULL) { 62 | uint32_t nodelen = MIN(MCP_INET_ADDRSTRLEN - 1, mcp_strlen(name)); 63 | mcp_memcpy(nodestr, name, nodelen); 64 | nodestr[nodelen] = '\0'; 65 | node = nodestr; 66 | } else { 67 | /* 68 | * If AI_PASSIVE flag is specified in hints.ai_flags, and node is 69 | * NULL, then the returned socket addresses will be suitable for 70 | * bind(2)ing a socket that will accept(2) connections. The returned 71 | * socket address will contain the wildcard IP address. 72 | */ 73 | node = NULL; 74 | hints.ai_flags |= AI_PASSIVE; 75 | } 76 | 77 | mcp_snprintf(service, MCP_UINTMAX_MAXLEN, "%d", port); 78 | 79 | status = getaddrinfo(node, service, &hints, &ai); 80 | if (status < 0) { 81 | log_error("address resolution of node '%s' service '%s' failed: %s", 82 | node, service, gai_strerror(status)); 83 | return -1; 84 | } 85 | 86 | /* 87 | * getaddrinfo() can return a linked list of more than one addrinfo, 88 | * since we requested for both AF_INET and AF_INET6 addresses and the 89 | * host itself can be multi-homed. Since we don't care whether we are 90 | * using ipv4 or ipv6, we just use the first address from this collection 91 | * in the order in which it was returned. 92 | * 93 | * The sorting function used within getaddrinfo() is defined in RFC 3484; 94 | * the order can be tweaked for a particular system by editing 95 | * /etc/gai.conf 96 | */ 97 | for (cai = ai, found = false; cai != NULL; cai = cai->ai_next) { 98 | si->family = cai->ai_family; 99 | si->addrlen = cai->ai_addrlen; 100 | mcp_memcpy(&si->addr, cai->ai_addr, si->addrlen); 101 | found = true; 102 | break; 103 | } 104 | 105 | freeaddrinfo(ai); 106 | 107 | return !found ? -1 : 0; 108 | } 109 | 110 | static int 111 | mcp_resolve_addr_unix(char *name, struct sockinfo *si) 112 | { 113 | struct sockaddr_un *un; 114 | uint32_t namelen = mcp_strlen(name); 115 | 116 | if (namelen >= MCP_UNIX_ADDRSTRLEN) { 117 | return -1; 118 | } 119 | 120 | un = &si->addr.un; 121 | 122 | un->sun_family = AF_UNIX; 123 | mcp_memcpy(un->sun_path, name, namelen); 124 | un->sun_path[namelen] = '\0'; 125 | 126 | si->family = AF_UNIX; 127 | si->addrlen = sizeof(*un); 128 | /* si->addr is an alias of un */ 129 | 130 | return 0; 131 | } 132 | 133 | /* 134 | * Resolve a hostname and service by translating it to socket address 135 | * and return it in si 136 | * 137 | * This routine is reentrant 138 | */ 139 | int 140 | mcp_resolve_addr(char *name, int port, struct sockinfo *si) 141 | { 142 | if (name != NULL && name[0] == '/') { 143 | return mcp_resolve_addr_unix(name, si); 144 | } 145 | 146 | return mcp_resolve_addr_inet(name, port, si); 147 | } 148 | 149 | int 150 | mcp_set_nonblocking(int sd) 151 | { 152 | int flags; 153 | 154 | flags = fcntl(sd, F_GETFL, 0); 155 | if (flags < 0) { 156 | return flags; 157 | } 158 | 159 | return fcntl(sd, F_SETFL, flags | O_NONBLOCK); 160 | } 161 | 162 | int 163 | mcp_set_tcpnodelay(int sd) 164 | { 165 | int nodelay; 166 | socklen_t len; 167 | 168 | nodelay = 1; 169 | len = sizeof(nodelay); 170 | 171 | return setsockopt(sd, IPPROTO_TCP, TCP_NODELAY, &nodelay, len); 172 | } 173 | 174 | int 175 | mcp_set_linger(int sd, int timeout) 176 | { 177 | struct linger linger; 178 | socklen_t len; 179 | 180 | linger.l_onoff = 1; 181 | linger.l_linger = timeout; 182 | 183 | len = sizeof(linger); 184 | 185 | return setsockopt(sd, SOL_SOCKET, SO_LINGER, &linger, len); 186 | } 187 | 188 | int 189 | mcp_set_sndbuf(int sd, int size) 190 | { 191 | socklen_t len; 192 | 193 | len = sizeof(size); 194 | 195 | return setsockopt(sd, SOL_SOCKET, SO_SNDBUF, &size, len); 196 | } 197 | 198 | int 199 | mcp_set_rcvbuf(int sd, int size) 200 | { 201 | socklen_t len; 202 | 203 | len = sizeof(size); 204 | 205 | return setsockopt(sd, SOL_SOCKET, SO_RCVBUF, &size, len); 206 | } 207 | 208 | int 209 | mcp_get_soerror(int sd) 210 | { 211 | int status, err; 212 | socklen_t len; 213 | 214 | err = 0; 215 | len = sizeof(err); 216 | 217 | status = getsockopt(sd, SOL_SOCKET, SO_ERROR, &err, &len); 218 | if (status == 0) { 219 | errno = err; 220 | } 221 | 222 | return status; 223 | } 224 | 225 | int 226 | mcp_get_sndbuf(int sd) 227 | { 228 | int status, size; 229 | socklen_t len; 230 | 231 | size = 0; 232 | len = sizeof(size); 233 | 234 | status = getsockopt(sd, SOL_SOCKET, SO_SNDBUF, &size, &len); 235 | if (status < 0) { 236 | return status; 237 | } 238 | 239 | return size; 240 | } 241 | 242 | int 243 | mcp_get_rcvbuf(int sd) 244 | { 245 | int status, size; 246 | socklen_t len; 247 | 248 | size = 0; 249 | len = sizeof(size); 250 | 251 | status = getsockopt(sd, SOL_SOCKET, SO_RCVBUF, &size, &len); 252 | if (status < 0) { 253 | return status; 254 | } 255 | 256 | return size; 257 | } 258 | 259 | bool 260 | mcp_valid_port(int n) 261 | { 262 | if (n < 1 || n > UINT16_MAX) { 263 | return false; 264 | } 265 | 266 | return true; 267 | } 268 | 269 | /* 270 | * Convert ascii representation of a positive integer to int. On error 271 | * return -1 272 | */ 273 | int 274 | mcp_atoi(char *line) 275 | { 276 | int errno_save, val; 277 | unsigned long int value; 278 | char *end; 279 | 280 | errno_save = errno; 281 | 282 | errno = 0; 283 | 284 | /* 285 | * unsigned long int strtoul(const char *nptr, char **endptr, int base); 286 | * 287 | * If endptr is not NULL, strtoul() stores the address of the first 288 | * invalid character in *endptr. If there were no digits at all, strtoul() 289 | * stores the original value of nptr in *endptr (and returns 0). 290 | * 291 | * In particular, if *nptr is not '\0' but **endptr is '\0' on return, the 292 | * entire string is valid. 293 | */ 294 | 295 | value = strtoul(line, &end, 10); 296 | if (errno != 0 || end == line || *end != '\0') { 297 | val = -1; 298 | } else { 299 | val = (int)value; 300 | } 301 | 302 | errno = errno_save; 303 | 304 | return val; 305 | } 306 | 307 | /* 308 | * Convert ascii representation of a positive floating pont number to 309 | * a double. On error return -1.0 310 | */ 311 | double 312 | mcp_atod(char *line) 313 | { 314 | int errno_save; 315 | double value; 316 | char *end; 317 | 318 | errno_save = errno; 319 | 320 | errno = 0; 321 | 322 | /* 323 | * double strtod(const char *nptr, char **endptr); 324 | * 325 | * If endptr is not NULL, a pointer to the character after the last 326 | * character used in the conversion is stored in the location referenced 327 | * by endptr. 328 | * 329 | * If no conversion is performed, zero is returned and the value of 330 | * nptr is stored in the location referenced by endptr. 331 | * 332 | * If the correct value would cause overflow, plus or minus HUGE_VAL 333 | * (HUGE_VALF, HUGE_VALL) is returned (according to the sign of the 334 | * value), and ERANGE is stored in errno. If the correct value would 335 | * cause underflow, zero is returned and ERANGE is stored in errno. 336 | */ 337 | value = strtod(line, &end); 338 | if (errno != 0 || end == line || *end != '\0') { 339 | value = -1.0; 340 | } 341 | 342 | errno = errno_save; 343 | 344 | return value; 345 | } 346 | 347 | void * 348 | _mcp_alloc(size_t size, char *name, int line) 349 | { 350 | void *p; 351 | 352 | ASSERT(size != 0); 353 | 354 | p = malloc(size); 355 | if (p == NULL) { 356 | log_debug(LOG_ERR, "malloc(%zu) failed @ %s:%d", size, name, line); 357 | } 358 | 359 | return p; 360 | } 361 | 362 | void * 363 | _mcp_zalloc(size_t size, char *name, int line) 364 | { 365 | void *p; 366 | 367 | p = mcp_alloc(size); 368 | if (p != NULL) { 369 | memset(p, 0, size); 370 | } 371 | 372 | return p; 373 | } 374 | 375 | void * 376 | _mcp_calloc(size_t nmemb, size_t size, char *name, int line) 377 | { 378 | return _mcp_zalloc(nmemb * size, name, line); 379 | } 380 | 381 | void * 382 | _mcp_realloc(void *ptr, size_t size, char *name, int line) 383 | { 384 | void *p; 385 | 386 | ASSERT(size != 0); 387 | 388 | p = realloc(ptr, size); 389 | if (p == NULL) { 390 | log_debug(LOG_CRIT, "realloc(%zu) failed @ %s:%d", size, name, line); 391 | } 392 | 393 | return p; 394 | } 395 | 396 | void 397 | _mcp_free(void *ptr) 398 | { 399 | ASSERT(ptr != NULL); 400 | free(ptr); 401 | } 402 | 403 | void 404 | mcp_stacktrace(int skip_count) 405 | { 406 | void *stack[64]; 407 | char **symbols; 408 | int size, i, j; 409 | 410 | size = backtrace(stack, 64); 411 | symbols = backtrace_symbols(stack, size); 412 | if (symbols == NULL) { 413 | return; 414 | } 415 | 416 | skip_count++; /* skip the current frame also */ 417 | 418 | for (i = skip_count, j = 0; i < size; i++, j++) { 419 | loga("[%d] %s", j, symbols[i]); 420 | } 421 | 422 | free(symbols); 423 | } 424 | 425 | void 426 | mcp_assert(const char *cond, const char *file, int line, int panic) 427 | { 428 | log_error("assert '%s' failed @ (%s, %d)", cond, file, line); 429 | if (panic) { 430 | mcp_stacktrace(1); 431 | abort(); 432 | } 433 | } 434 | 435 | int 436 | _vscnprintf(char *buf, size_t size, const char *fmt, va_list args) 437 | { 438 | int n; 439 | 440 | n = vsnprintf(buf, size, fmt, args); 441 | 442 | /* 443 | * The return value is the number of characters which would be written 444 | * into buf not including the trailing '\0'. If size is == 0 the 445 | * function returns 0. 446 | * 447 | * On error, the function also returns 0. This is to allow idiom such 448 | * as len += _vscnprintf(...) 449 | * 450 | * See: http://lwn.net/Articles/69419/ 451 | */ 452 | if (n <= 0) { 453 | return 0; 454 | } 455 | 456 | if (n < (int) size) { 457 | return n; 458 | } 459 | 460 | return (int)(size - 1); 461 | } 462 | 463 | int 464 | _scnprintf(char *buf, size_t size, const char *fmt, ...) 465 | { 466 | va_list args; 467 | int n; 468 | 469 | va_start(args, fmt); 470 | n = _vscnprintf(buf, size, fmt, args); 471 | va_end(args); 472 | 473 | return n; 474 | } 475 | 476 | /* 477 | * Send n bytes on a blocking descriptor 478 | */ 479 | ssize_t 480 | _mcp_sendn(int sd, const void *vptr, size_t n) 481 | { 482 | size_t nleft; 483 | ssize_t nsend; 484 | const char *ptr; 485 | 486 | ptr = vptr; 487 | nleft = n; 488 | while (nleft > 0) { 489 | nsend = send(sd, ptr, nleft, 0); 490 | if (nsend < 0) { 491 | if (errno == EINTR) { 492 | continue; 493 | } 494 | return nsend; 495 | } 496 | if (nsend == 0) { 497 | return -1; 498 | } 499 | 500 | nleft -= (size_t)nsend; 501 | ptr += nsend; 502 | } 503 | 504 | return (ssize_t)n; 505 | } 506 | 507 | /* 508 | * Recv n bytes from a blocking descriptor 509 | */ 510 | ssize_t 511 | _mcp_recvn(int sd, void *vptr, size_t n) 512 | { 513 | size_t nleft; 514 | ssize_t nrecv; 515 | char *ptr; 516 | 517 | ptr = vptr; 518 | nleft = n; 519 | while (nleft > 0) { 520 | nrecv = recv(sd, ptr, nleft, 0); 521 | if (nrecv < 0) { 522 | if (errno == EINTR) { 523 | continue; 524 | } 525 | return nrecv; 526 | } 527 | if (nrecv == 0) { 528 | break; 529 | } 530 | 531 | nleft -= (size_t)nrecv; 532 | ptr += nrecv; 533 | } 534 | 535 | return (ssize_t)(n - nleft); 536 | } 537 | 538 | /* 539 | * Return the current time in microseconds since Epoch 540 | */ 541 | int64_t 542 | mcp_usec_now(void) 543 | { 544 | struct timeval now; 545 | int64_t usec; 546 | int status; 547 | 548 | status = gettimeofday(&now, NULL); 549 | if (status < 0) { 550 | log_error("gettimeofday failed: %s", strerror(errno)); 551 | return -1; 552 | } 553 | 554 | usec = (int64_t)now.tv_sec * 1000000LL + (int64_t)now.tv_usec; 555 | 556 | return usec; 557 | } 558 | -------------------------------------------------------------------------------- /src/mcp_stats.c: -------------------------------------------------------------------------------- 1 | /* 2 | * twemperf - a tool for measuring memcached server performance. 3 | * Copyright (C) 2011 Twitter, Inc. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include 20 | #include 21 | 22 | #include 23 | #include 24 | 25 | static void 26 | stats_rusage_start(struct context *ctx) 27 | { 28 | int status; 29 | struct stats *stats = &ctx->stats; 30 | 31 | status = getrusage(RUSAGE_SELF, &stats->rusage_start); 32 | if (status < 0) { 33 | log_panic("getrusage failed: %s", strerror(errno)); 34 | } 35 | } 36 | 37 | static void 38 | stats_rusage_stop(struct context *ctx) 39 | { 40 | int status; 41 | struct stats *stats = &ctx->stats; 42 | 43 | status = getrusage(RUSAGE_SELF, &stats->rusage_stop); 44 | if (status < 0) { 45 | log_panic("getrusage failed: %s", strerror(errno)); 46 | } 47 | } 48 | 49 | static void 50 | stats_rusage_print(struct context *ctx) 51 | { 52 | struct opt *opt = &ctx->opt; 53 | struct stats *stats = &ctx->stats; 54 | struct rusage *start, *stop; 55 | double delta, user, sys; 56 | long int maxrss, ixrss, idrss, isrss; 57 | long int minflt, majflt; 58 | long int nswap; 59 | long int inblock, oublock; 60 | long int msgsnd, msgrcv; 61 | long int nsignals; 62 | long int nvcsw, nivcsw; 63 | 64 | delta = stats->stop_time - stats->start_time; 65 | 66 | start = &stats->rusage_start; 67 | stop = &stats->rusage_stop; 68 | 69 | /* total amount of user time used */ 70 | user = TV_TO_SEC(&stop->ru_utime) - TV_TO_SEC(&start->ru_utime); 71 | 72 | /* total amount of system time used */ 73 | sys = TV_TO_SEC(&stop->ru_stime) - TV_TO_SEC(&start->ru_stime); 74 | 75 | log_stderr("CPU time [s]: user %.2f system %.2f (user %.1f%% system " 76 | "%.1f%% total %.1f%%)", user, sys, 100.0 * user / delta, 77 | 100.0 * sys / delta, 100.0 * (user + sys) / delta); 78 | 79 | if (!opt->print_rusage) { 80 | return; 81 | } 82 | 83 | /* maximum resident set size (in kilobytes) */ 84 | maxrss = stop->ru_maxrss - start->ru_maxrss; 85 | 86 | /* 87 | * Amount of sharing of text segment memory with other 88 | * processes (kilobyte-seconds) 89 | */ 90 | ixrss = stop->ru_ixrss - start->ru_ixrss; 91 | 92 | /* amount of data segment memory used (kilobyte-seconds) */ 93 | idrss = stop->ru_idrss - start->ru_idrss; 94 | 95 | /* amount of stack memory used (kilobyte-seconds) */ 96 | isrss = stop->ru_isrss - start->ru_isrss; 97 | 98 | /* 99 | * Number of soft page faults (i.e. those serviced by reclaiming 100 | * a page from the list of pages awaiting reallocation 101 | */ 102 | minflt = stop->ru_minflt - start->ru_minflt; 103 | 104 | /* number of hard page faults (i.e. those that required I/O) */ 105 | majflt = stop->ru_majflt - start->ru_majflt; 106 | 107 | /* number of times a process was swapped out of physical memory */ 108 | nswap = stop->ru_nswap - start->ru_nswap; 109 | 110 | /* number of input operations via the file system */ 111 | inblock = stop->ru_inblock - start->ru_inblock; 112 | 113 | /* number of output operations via the file system */ 114 | oublock = stop->ru_oublock - start->ru_oublock; 115 | 116 | /* number of IPC messages sent */ 117 | msgsnd = stop->ru_msgsnd - start->ru_msgsnd; 118 | 119 | /* number of IPC messages received */ 120 | msgrcv = stop->ru_msgrcv - start->ru_msgrcv; 121 | 122 | /* number of signals delivered */ 123 | nsignals = stop->ru_nsignals - start->ru_nsignals; 124 | 125 | /* 126 | * Number of voluntary context switches, i.e. because the process 127 | * gave up the process before it had to (usually to wait for some 128 | * resource to be available) 129 | */ 130 | nvcsw = stop->ru_nvcsw - start->ru_nvcsw; 131 | 132 | /* 133 | * Number of involuntary context switches, i.e. a higher priority process 134 | * became runnable or the current process used up its time slice 135 | */ 136 | nivcsw = stop->ru_nivcsw - start->ru_nivcsw; 137 | 138 | log_stderr("Maximum resident set size [KB]: %ld", maxrss); 139 | log_stderr("Text segment shared with other processes [KB-sec]: %ld", ixrss); 140 | log_stderr("Data segment used [KB-sec]: %ld", idrss); 141 | log_stderr("Stack memory used [KB-sec]: %ld", isrss); 142 | log_stderr("Number of soft page faults: %ld", minflt); 143 | log_stderr("Number of hard page faults: %ld", majflt); 144 | log_stderr("Number of times process was swapped out of physical memory: %ld", nswap); 145 | log_stderr("Number of input operations via file system: %ld", inblock); 146 | log_stderr("Number of output operations via file system: %ld", oublock); 147 | log_stderr("Number of IPC messages sent: %ld", msgsnd); 148 | log_stderr("Number of IPC messages received: %ld", msgrcv); 149 | log_stderr("Number of signals delivered: %ld", nsignals); 150 | log_stderr("Number of voluntary context switches: %ld", nvcsw); 151 | log_stderr("Number of involuntary context switches: %ld", nivcsw); 152 | } 153 | 154 | void 155 | stats_init(struct context *ctx) 156 | { 157 | struct stats *stats = &ctx->stats; 158 | uint32_t i; 159 | 160 | memset(&stats->rusage_start, 0, sizeof(stats->rusage_start)); 161 | memset(&stats->rusage_stop, 0, sizeof(stats->rusage_stop)); 162 | 163 | stats->start_time = 0.0; 164 | stats->stop_time = 0.0; 165 | 166 | stats->nconn_created = 0; 167 | stats->nconn_destroyed = 0; 168 | 169 | stats->nconn_active = 0; 170 | stats->nconn_active_max = 0; 171 | 172 | stats->nconnect_issued = 0; 173 | stats->nconnect = 0; 174 | stats->connect_sum = 0.0; 175 | stats->connect_sum2 = 0.0; 176 | stats->connect_min = DBL_MAX; 177 | stats->connect_max = 0.0; 178 | stats->connection_sum = 0.0; 179 | stats->connection_sum2 = 0.0; 180 | stats->connection_min = DBL_MAX; 181 | stats->connection_max = 0.0; 182 | 183 | stats->nclient_timeout = 0; 184 | stats->nsock_fdunavail = 0; 185 | stats->nsock_ftabfull = 0; 186 | stats->nsock_addrunavail = 0; 187 | stats->nsock_refused = 0; 188 | stats->nsock_reset = 0; 189 | stats->nsock_timedout = 0; 190 | stats->nsock_other_error = 0; 191 | 192 | stats->nreq = 0; 193 | stats->req_bytes_sent = 0.0; 194 | stats->req_bytes_sent2 = 0.0; 195 | stats->req_bytes_sent_min = DBL_MAX; 196 | stats->req_bytes_sent_max = 0.0; 197 | 198 | stats->req_xfer_sum = 0.0; 199 | stats->req_xfer_sum2 = 0.0; 200 | stats->req_xfer_min = DBL_MAX; 201 | stats->req_xfer_max = 0.0; 202 | 203 | stats->req_rsp_sum = 0.0; 204 | stats->req_rsp_sum2 = 0.0; 205 | stats->req_rsp_min = DBL_MAX; 206 | stats->req_rsp_max = 0.0; 207 | for (i = 0; i < HIST_NUM_BINS; i++) { 208 | stats->req_rsp_hist[i] = 0; 209 | } 210 | 211 | stats->nrsp = 0; 212 | stats->rsp_bytes_rcvd = 0.0; 213 | stats->rsp_bytes_rcvd2 = 0.0; 214 | stats->rsp_bytes_rcvd_min = DBL_MAX; 215 | stats->rsp_bytes_rcvd_max = 0.0; 216 | 217 | stats->rsp_xfer_sum = 0.0; 218 | stats->rsp_xfer_sum2 = 0.0; 219 | stats->rsp_xfer_min = DBL_MAX; 220 | stats->rsp_xfer_max = 0.0; 221 | 222 | for (i = 0; i < RSP_MAX_TYPES; i++) { 223 | stats->rsp_type[i] = 0; 224 | } 225 | } 226 | 227 | void 228 | stats_start(struct context *ctx) 229 | { 230 | struct stats *stats = &ctx->stats; 231 | 232 | stats_rusage_start(ctx); 233 | stats->start_time = timer_now(); 234 | } 235 | 236 | void 237 | stats_stop(struct context *ctx) 238 | { 239 | struct stats *stats = &ctx->stats; 240 | 241 | stats_rusage_stop(ctx); 242 | stats->stop_time = timer_now(); 243 | } 244 | 245 | void 246 | stats_dump(struct context *ctx) 247 | { 248 | struct opt *opt = &ctx->opt; 249 | struct stats *stats = &ctx->stats; 250 | double conn_period, conn_rate; 251 | double conn_avg, conn_min, conn_max, conn_stddev; 252 | double connection_avg, connection_min, connection_max, connection_stddev; 253 | double req_rate, req_period; 254 | double req_size_avg, req_size_min, req_size_max, req_size_stddev; 255 | double rsp_rate, rsp_period; 256 | double rsp_size_avg, rsp_size_min, rsp_size_max, rsp_size_stddev; 257 | double req_rsp_avg, req_rsp_min, req_rsp_max, req_rsp_stddev; 258 | double req_rsp_p25 = 0.0, req_rsp_p50 = 0.0, req_rsp_p75 = 0.0; 259 | double req_rsp_p95 = 0.0, req_rsp_p99 = 0.0, req_rsp_p999 = 0.0; 260 | double total_size, total_rate; 261 | double delta; 262 | double bin_time; 263 | uint32_t i, nerror; 264 | long int n; 265 | 266 | /* stop stats collection */ 267 | stats_stop(ctx); 268 | 269 | ASSERT(stats->stop_time > stats->start_time); 270 | 271 | delta = stats->stop_time - stats->start_time; 272 | 273 | /* 274 | * Total Section 275 | * 1. number of successful TCP connections 276 | * 2. number of requests 277 | * 3. number of responses 278 | * 4. overall time spent testing 279 | */ 280 | log_stderr(""); 281 | log_stderr("Total: connections %"PRIu32" requests %"PRIu32" responses " 282 | "%"PRIu32" test-duration %.3f s", stats->nconnect_issued, 283 | stats->nreq, stats->nrsp, delta); 284 | 285 | /* 286 | * Connection Section 287 | * 1. rate at which new connections were initiated 288 | * 2. time period between any two successive connections, and 289 | * 3. number of concurrent connections open to the server 290 | * 4. connection time for successful connections 291 | * 5. connect time for successful connections 292 | */ 293 | if (stats->nconnect_issued != 0) { 294 | log_stderr(""); 295 | 296 | conn_period = delta / stats->nconnect_issued; 297 | conn_rate = stats->nconnect_issued / delta; 298 | 299 | log_stderr("Connection rate: %.1f conn/s (%.1f ms/conn <= %"PRIu32" " 300 | "concurrent connections)", conn_rate, 1e3 * conn_period, 301 | stats->nconn_active_max); 302 | 303 | connection_avg = stats->connection_sum / stats->nconnect; 304 | connection_min = stats->connection_min; 305 | connection_max = stats->connection_max; 306 | connection_stddev = STDDEV(stats->connection_sum, stats->connection_sum2, 307 | stats->nconnect); 308 | log_stderr("Connection time [ms]: avg %.1f min %.1f max %.1f stddev " 309 | "%.2f", 1e3 * connection_avg, 1e3 * connection_min, 310 | 1e3 * connection_max, 1e3 * connection_stddev); 311 | 312 | conn_avg = stats->connect_sum / stats->nconnect; 313 | conn_min = stats->connect_min; 314 | conn_max = stats->connect_max; 315 | conn_stddev = STDDEV(stats->connect_sum, stats->connect_sum2, 316 | stats->nconnect); 317 | log_stderr("Connect time [ms]: avg %.1f min %.1f max %.1f " 318 | "stddev %.2f", 1e3 * conn_avg, 1e3 * conn_min, 319 | 1e3 * conn_max, 1e3 * conn_stddev); 320 | } 321 | 322 | /* 323 | * Request section 324 | * 1. request rate - rate at which request were issued 325 | * 2. request size 326 | */ 327 | if (stats->nreq != 0) { 328 | log_stderr(""); 329 | 330 | req_period = delta / stats->nreq; 331 | req_rate = stats->nreq / delta; 332 | 333 | log_stderr("Request rate: %.1f req/s (%.1f ms/req)", req_rate, 334 | 1e3 * req_period); 335 | 336 | req_size_min = stats->req_bytes_sent_min; 337 | req_size_avg = stats->req_bytes_sent / stats->nreq; 338 | req_size_max = stats->req_bytes_sent_max; 339 | req_size_stddev = STDDEV(stats->req_bytes_sent, stats->req_bytes_sent2, 340 | stats->nreq); 341 | 342 | log_stderr("Request size [B]: avg %.1f min %.1f max %.1f stddev %.2f", 343 | req_size_avg, req_size_min, req_size_max, req_size_stddev); 344 | } 345 | 346 | /* 347 | * Response section 348 | * 1. response rate 349 | * 2. response size 350 | * 3. response time - how long it took for the server to respond 351 | * 4. response types 352 | */ 353 | if (stats->nrsp != 0) { 354 | log_stderr(""); 355 | 356 | rsp_period = delta / stats->nrsp; 357 | rsp_rate = stats->nrsp / delta; 358 | 359 | log_stderr("Response rate: %.1f rsp/s (%.1f ms/rsp)", rsp_rate, 360 | 1e3 * rsp_period); 361 | 362 | rsp_size_min = stats->rsp_bytes_rcvd_min; 363 | rsp_size_avg = stats->rsp_bytes_rcvd / stats->nrsp; 364 | rsp_size_max = stats->rsp_bytes_rcvd_max; 365 | rsp_size_stddev = STDDEV(stats->rsp_bytes_rcvd, stats->rsp_bytes_rcvd2, 366 | stats->nrsp); 367 | 368 | log_stderr("Response size [B]: avg %.1f min %.1f max %.1f stddev %.2f", 369 | rsp_size_avg, rsp_size_min, rsp_size_max, rsp_size_stddev); 370 | 371 | req_rsp_avg = stats->req_rsp_sum / stats->nrsp; 372 | req_rsp_min = stats->req_rsp_min; 373 | req_rsp_max = stats->req_rsp_max; 374 | req_rsp_stddev = STDDEV(stats->req_rsp_sum, stats->req_rsp_sum2, 375 | stats->nrsp); 376 | 377 | log_stderr("Response time [ms]: avg %.1f min %.1f max %.1f stddev %.2f", 378 | 1e3 * req_rsp_avg, 1e3 * req_rsp_min, 1e3 * req_rsp_max, 379 | req_rsp_stddev); 380 | 381 | if (opt->print_histogram) { 382 | log_stderr("Response time histogram [ms]:"); 383 | 384 | for (i = 0; i < HIST_NUM_BINS; i++) { 385 | if (stats->req_rsp_hist[i] == 0) { 386 | continue; 387 | } 388 | if (i > 0 && stats->req_rsp_hist[i - 1] == 0) { 389 | log_stderr("%14c", ':'); 390 | } 391 | bin_time = i * HIST_BIN_WIDTH; 392 | log_stderr("%16.1f %u", 1e3 * bin_time, stats->req_rsp_hist[i]); 393 | } 394 | if (stats->req_rsp_hist[i - 1] == 0) { 395 | log_stderr("%14c", ':'); 396 | } 397 | } 398 | 399 | for (i = 0, n = 0; i < HIST_NUM_BINS; i++) { 400 | n += stats->req_rsp_hist[i]; 401 | 402 | if (req_rsp_p25 == 0.0 && n >= lrint(0.25 * stats->nrsp)) { 403 | req_rsp_p25 = i * HIST_BIN_WIDTH; 404 | } 405 | 406 | if (req_rsp_p50 == 0.0 && n >= lrint(0.50 * stats->nrsp)) { 407 | req_rsp_p50 = i * HIST_BIN_WIDTH; 408 | } 409 | 410 | if (req_rsp_p75 == 0.0 && n >= lrint(0.70 * stats->nrsp)) { 411 | req_rsp_p75 = i * HIST_BIN_WIDTH; 412 | } 413 | 414 | if (req_rsp_p95 == 0.0 && n >= lrint(0.95 * stats->nrsp)) { 415 | req_rsp_p95 = i * HIST_BIN_WIDTH; 416 | } 417 | 418 | if (req_rsp_p99 == 0.0 && n >= lrint(0.99 * stats->nrsp)) { 419 | req_rsp_p99 = i * HIST_BIN_WIDTH; 420 | } 421 | 422 | if (req_rsp_p999 == 0.0 && n >= lrint(0.999 * stats->nrsp)) { 423 | req_rsp_p999 = i * HIST_BIN_WIDTH; 424 | } 425 | } 426 | 427 | log_stderr("Response time [ms]: p25 %.1f p50 %.1f p75 %.1f", 428 | 1e3 * req_rsp_p25, 1e3 * req_rsp_p50, 1e3 * req_rsp_p75); 429 | 430 | log_stderr("Response time [ms]: p95 %.1f p99 %.1f p999 %.1f", 431 | 1e3 * req_rsp_p95, 1e3 * req_rsp_p99, 1e3 * req_rsp_p999); 432 | 433 | log_stderr("Response type: stored %"PRIu32" not_stored %"PRIu32" " 434 | "exists %"PRIu32" not_found %"PRIu32"", 435 | stats->rsp_type[RSP_STORED], stats->rsp_type[RSP_NOT_STORED], 436 | stats->rsp_type[RSP_EXISTS], stats->rsp_type[RSP_NOT_FOUND]); 437 | 438 | log_stderr("Response type: num %"PRIu32" deleted %"PRIu32" end %"PRIu32 439 | " value %"PRIu32"", stats->rsp_type[RSP_NUM], 440 | stats->rsp_type[RSP_DELETED], stats->rsp_type[RSP_END], 441 | stats->rsp_type[RSP_VALUE]); 442 | 443 | log_stderr("Response type: error %"PRIu32" client_error %"PRIu32" " 444 | "server_error %"PRIu32"", stats->rsp_type[RSP_ERROR], 445 | stats->rsp_type[RSP_CLIENT_ERROR], 446 | stats->rsp_type[RSP_SERVER_ERROR]); 447 | } 448 | 449 | /* 450 | * Error section 451 | */ 452 | log_stderr(""); 453 | 454 | nerror = stats->nclient_timeout + stats->nsock_fdunavail + 455 | stats->nsock_ftabfull + stats->nsock_addrunavail + 456 | stats->nsock_refused + stats->nsock_reset + 457 | stats->nsock_timedout + stats->nsock_other_error; 458 | 459 | log_stderr("Errors: total %"PRIu32" client-timo %"PRIu32" " 460 | "socket-timo %"PRIu32" connrefused %"PRIu32" " 461 | "connreset %"PRIu32"", nerror, stats->nclient_timeout, 462 | stats->nsock_timedout, stats->nsock_refused, 463 | stats->nsock_reset); 464 | 465 | log_stderr("Errors: fd-unavail %"PRIu32" ftab-full %"PRIu32" " 466 | "addrunavail %"PRIu32" other %"PRIu32"", 467 | stats->nsock_fdunavail, stats->nsock_ftabfull, 468 | stats->nsock_addrunavail, stats->nsock_other_error); 469 | 470 | /* 471 | * Resource usage section 472 | * 1. CPU utilization 473 | * 2. Net I/O 474 | */ 475 | log_stderr(""); 476 | 477 | stats_rusage_print(ctx); 478 | 479 | if (stats->nreq + stats->nrsp != 0) { 480 | double total; 481 | char *metric; 482 | 483 | total_size = stats->req_bytes_sent + stats->rsp_bytes_rcvd; 484 | total_rate = total_size / delta; 485 | 486 | if (total_size <= KB) { 487 | total = total_size; 488 | metric = "B"; 489 | } else if (total_size <= MB) { 490 | total = total_size / KB; 491 | metric = "KB"; 492 | } else if (total_size <= GB) { 493 | total = total_size / MB; 494 | metric = "MB"; 495 | } else { 496 | total = total_size / GB; 497 | metric = "GB"; 498 | } 499 | 500 | log_stderr("Net I/O: bytes %.1f %s rate %.1f KB/s (%.1f*10^6 bps)", 501 | total, metric, total_rate / 1024.0, 502 | 8e-6 * total_size / delta); 503 | } 504 | 505 | log_stderr(""); 506 | 507 | exit(0); 508 | } 509 | -------------------------------------------------------------------------------- /src/mcp.c: -------------------------------------------------------------------------------- 1 | /* 2 | * twemperf - a tool for measuring memcached server performance. 3 | * Copyright (C) 2011 Twitter, Inc. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | extern struct string req_strings[]; 26 | 27 | static int show_help; 28 | static int show_version; 29 | 30 | #define MCP_LOG_DEFAULT LOG_NOTICE 31 | #define MCP_LOG_MIN LOG_EMERG 32 | #define MCP_LOG_MAX LOG_PVERB 33 | #define MCP_LOG_PATH "stderr" 34 | 35 | #define MCP_SERVER "localhost" 36 | #define MCP_PORT 11211 37 | 38 | #define MCP_CLIENT_ID 0 39 | #define MCP_CLIENT_N 1 40 | 41 | #define MCP_METHOD_STR "set" 42 | #define MCP_METHOD REQ_SET 43 | 44 | #define MCP_EXPIRY_STR "0" 45 | #define MCP_EXPIRY 0 46 | 47 | #define MCP_PREFIX "mcp:" 48 | #define MCP_PREFIX_LEN CALL_PREFIX_LEN 49 | 50 | #define MCP_TIMEOUT 0.0 51 | #define MCP_TIMEOUT_STR "0.0" 52 | 53 | #define MCP_LINGER_STR "off" 54 | #define MCP_LINGER 0 55 | 56 | #define MCP_SEND_BUFSIZE 4096 57 | #define MCP_RECV_BUFSIZE 16384 58 | 59 | #define MCP_NUM_CONNS 1 60 | #define MCP_NUM_CALLS 1 61 | 62 | #define MCP_CONN_DIST_STR "0" 63 | #define MCP_CONN_DIST DIST_NONE 64 | #define MCP_CONN_DIST_MIN 0.0 65 | #define MCP_CONN_DIST_MAX 0.0 66 | 67 | #define MCP_CALL_DIST_STR "0" 68 | #define MCP_CALL_DIST DIST_NONE 69 | #define MCP_CALL_DIST_MIN 0.0 70 | #define MCP_CALL_DIST_MAX 0.0 71 | 72 | #define MCP_SIZE_DIST_STR "d1" 73 | #define MCP_SIZE_DIST DIST_DETERMINISTIC 74 | #define MCP_SIZE_DIST_MIN 1 75 | #define MCP_SIZE_DIST_MAX 1 76 | 77 | #define MCP_PRINT_RUSAGE 0 78 | 79 | static struct option long_options[] = { 80 | { "help", no_argument, NULL, 'h' }, 81 | { "version", no_argument, NULL, 'V' }, 82 | { "verbosity", required_argument, NULL, 'v' }, 83 | { "output", required_argument, NULL, 'o' }, 84 | { "server", required_argument, NULL, 's' }, 85 | { "port", required_argument, NULL, 'p' }, 86 | { "print-histogram", no_argument, NULL, 'H' }, 87 | { "timeout", required_argument, NULL, 't' }, 88 | { "linger", required_argument, NULL, 'l' }, 89 | { "send-buffer", required_argument, NULL, 'b' }, 90 | { "recv-buffer", required_argument, NULL, 'B' }, 91 | { "disable-nodelay", no_argument, NULL, 'D' }, 92 | { "method", required_argument, NULL, 'm' }, 93 | { "expiry", required_argument, NULL, 'e' }, 94 | { "use-noreply", no_argument, NULL, 'q' }, 95 | { "prefix", required_argument, NULL, 'P' }, 96 | { "client", required_argument, NULL, 'c' }, 97 | { "num-conns", required_argument, NULL, 'n' }, 98 | { "num-calls", required_argument, NULL, 'N' }, 99 | { "conn-rate", required_argument, NULL, 'r' }, 100 | { "call-rate", required_argument, NULL, 'R' }, 101 | { "sizes", required_argument, NULL, 'z' }, 102 | { NULL, 0, NULL, 0 } 103 | }; 104 | 105 | static char short_options[] = "hVv:o:s:p:Ht:l:b:B:Dm:e:qP:c:n:N:r:R:z:"; 106 | 107 | static void 108 | mcp_show_usage(void) 109 | { 110 | log_stderr( 111 | "Usage: mcperf [-?hV] [-v verbosity level] [-o output file]" CRLF 112 | " [-s server] [-p port] [-H] [-t timeout] [-l linger]" CRLF 113 | " [-b send-buffer] [-B recv-buffer] [-D]" CRLF 114 | " [-m method] [-e expiry] [-q] [-P prefix]" CRLF 115 | " [-c client] [-n num-conns] [-N num-calls]" CRLF 116 | " [-r conn-rate] [-R call-rate] [-z sizes]" CRLF 117 | "" CRLF 118 | "Options:" CRLF 119 | " -h, --help : this help" CRLF 120 | " -V, --version : show version and exit" CRLF 121 | " -v, --verbosity=N : set logging level (default: %d, min: %d, max: %d)" CRLF 122 | " -o, --output=S : set logging file (default: %s)" CRLF 123 | " -s, --server=S : set the hostname of the server (default: %s)" CRLF 124 | " -p, --port=N : set the port number of the server (default: %d)" CRLF 125 | " -H, --print-histogram : print response time histogram" CRLF 126 | " ...", 127 | MCP_LOG_DEFAULT, MCP_LOG_MIN, MCP_LOG_MAX, MCP_LOG_PATH, 128 | MCP_SERVER, MCP_PORT); 129 | 130 | log_stderr( 131 | " -t, --timeout=X : set the connection and response timeout in sec (default: %s sec)" CRLF 132 | " -l, --linger=N : set the linger timeout in sec, when closing TCP connections (default: %s)" CRLF 133 | " -b, --send-buffer=N : set socket send buffer size (default: %d bytes)" CRLF 134 | " -B, --recv-buffer=N : set socket recv buffer size (default: %d bytes)" CRLF 135 | " -D, --disable-nodelay : disable tcp nodelay" CRLF 136 | " ...", 137 | MCP_TIMEOUT_STR, MCP_LINGER_STR, 138 | MCP_SEND_BUFSIZE, MCP_RECV_BUFSIZE 139 | ); 140 | 141 | log_stderr( 142 | " -m, --method=M : set the method to use when issuing memcached request (default: %s)" CRLF 143 | " -e, --expiry=N : set the expiry value in sec for generated requests (default: %s sec)" CRLF 144 | " -q, --use-noreply : set noreply for generated requests" CRLF 145 | " -P, --prefix=S : set the prefix of generated keys (default: %s)" CRLF 146 | " ...", 147 | MCP_METHOD_STR, MCP_EXPIRY_STR, 148 | MCP_PREFIX 149 | ); 150 | 151 | log_stderr( 152 | " -c, --client=I/N : set mcperf instance to be I out of total N instances (default: %d/%d)" CRLF 153 | " -n, --num-conns=N : set the number of connections to create (default: %d)" CRLF 154 | " -N, --num-calls=N : set the number of calls to create on each connection (default: %d)" CRLF 155 | " -r, --conn-rate=R : set the connection creation rate (default: %s conns/sec) "CRLF 156 | " -R, --call-rate=R : set the call creation rate (default: %s calls/sec)" CRLF 157 | " -z, --sizes=R : set the distribution for item sizes (default: %s bytes)" CRLF 158 | " ...", 159 | MCP_CLIENT_ID, MCP_CLIENT_N, MCP_NUM_CONNS, MCP_NUM_CALLS, 160 | MCP_CONN_DIST_STR, MCP_CALL_DIST_STR, MCP_SIZE_DIST_STR 161 | ); 162 | 163 | log_stderr( 164 | "Where:" CRLF 165 | " N is an integer" CRLF 166 | " X is a real" CRLF 167 | " S is a string" CRLF 168 | " M is a method string and is either a 'get', 'gets', 'delete', 'cas', 'set', 'add', 'replace'" CRLF 169 | " 'append', 'prepend', 'incr', 'decr'" CRLF 170 | " R is the rate written as [D]R1[,R2] where:" CRLF 171 | " D is the distribution type and is either deterministic 'd', uniform 'u', or exponential 'e' and if:" CRLF 172 | " D is ommited or set to 'd', a deterministic interval specified by parameter R1 is used" CRLF 173 | " D is set to 'e', an exponential distibution with mean interval of R1 is used" CRLF 174 | " D is set to 'u', a uniform distribution over interval [R1, R2) is used" CRLF 175 | " R is 0, the next request or connection is created after the previous one completes" CRLF 176 | " " 177 | ); 178 | } 179 | 180 | static void 181 | mcp_set_default_options(struct context *ctx) 182 | { 183 | struct opt *opt = &ctx->opt; 184 | 185 | opt->log_level = MCP_LOG_DEFAULT; 186 | opt->log_filename = NULL; 187 | 188 | /* default server info */ 189 | opt->server = MCP_SERVER; 190 | opt->port = MCP_PORT; 191 | memset(&opt->si, 0, sizeof(opt->si)); 192 | 193 | opt->print_histogram = 0; 194 | 195 | opt->timeout = MCP_TIMEOUT; 196 | /* opt->linger_timeout is don't-care when lingering is off */ 197 | opt->linger = MCP_LINGER; 198 | opt->send_buf_size = MCP_SEND_BUFSIZE; 199 | opt->recv_buf_size = MCP_RECV_BUFSIZE; 200 | opt->disable_nodelay = 0; 201 | 202 | opt->method = MCP_METHOD; 203 | opt->expiry = MCP_EXPIRY; 204 | opt->use_noreply = 0; 205 | opt->prefix.data = MCP_PREFIX; 206 | opt->prefix.len = sizeof(MCP_PREFIX) - 1; 207 | 208 | /* default client id */ 209 | opt->client.id = MCP_CLIENT_ID; 210 | opt->client.n = MCP_CLIENT_N; 211 | 212 | /* default connection generator */ 213 | opt->num_conns = MCP_NUM_CONNS; 214 | opt->conn_dopt.type = MCP_CONN_DIST; 215 | opt->conn_dopt.min = MCP_CONN_DIST_MIN; 216 | opt->conn_dopt.max = MCP_CONN_DIST_MAX; 217 | 218 | /* default call generator */ 219 | opt->num_calls = MCP_NUM_CALLS; 220 | opt->call_dopt.type = MCP_CALL_DIST; 221 | opt->call_dopt.min = MCP_CALL_DIST_MIN; 222 | opt->call_dopt.max = MCP_CALL_DIST_MAX; 223 | 224 | /* default size generator */ 225 | opt->size_dopt.type = MCP_SIZE_DIST; 226 | opt->size_dopt.min = MCP_SIZE_DIST_MIN; 227 | opt->size_dopt.max = MCP_SIZE_DIST_MAX; 228 | 229 | opt->print_rusage = MCP_PRINT_RUSAGE; 230 | } 231 | 232 | static rstatus_t 233 | mcp_get_dist_opt(struct dist_opt *dopt, char *line) 234 | { 235 | char *pos; 236 | 237 | /* 238 | * Parse any distribution value specified as: 239 | * --distribution [d|u|e]T1[,T2] 240 | */ 241 | switch (*line) { 242 | case 'd': 243 | dopt->type = DIST_DETERMINISTIC; 244 | line++; 245 | break; 246 | 247 | case 'u': 248 | dopt->type = DIST_UNIFORM; 249 | line++; 250 | break; 251 | 252 | case 'e': 253 | dopt->type = DIST_EXPONENTIAL; 254 | line++; 255 | break; 256 | 257 | case 's': 258 | dopt->type = DIST_SEQUENTIAL; 259 | line++; 260 | break; 261 | 262 | default: 263 | dopt->type = DIST_NONE; 264 | break; 265 | } 266 | dopt->min = 0.0; 267 | dopt->max = 0.0; 268 | 269 | switch (dopt->type) { 270 | case DIST_NONE: 271 | dopt->min = mcp_atod(line); 272 | if (dopt->min < 0.0) { 273 | log_stderr("mcperf: invalid distribution value '%s'", line); 274 | return MCP_ERROR; 275 | } 276 | if (dopt->min != 0.0) { 277 | dopt->type = DIST_DETERMINISTIC; 278 | dopt->min = 1.0 / dopt->min; 279 | dopt->max = dopt->min; 280 | } 281 | break; 282 | 283 | case DIST_DETERMINISTIC: 284 | case DIST_EXPONENTIAL: 285 | case DIST_SEQUENTIAL: 286 | dopt->min = mcp_atod(line); 287 | if (dopt->min <= 0.0) { 288 | log_stderr("mcperf: invalid mean value '%s'", line); 289 | return MCP_ERROR; 290 | } 291 | dopt->max = dopt->min; 292 | break; 293 | 294 | case DIST_UNIFORM: 295 | pos = strchr(line, ','); 296 | if (pos == NULL) { 297 | log_stderr("mcperf: invalid uniform distribution value '%s'", line); 298 | return MCP_ERROR; 299 | } 300 | *pos = '\0'; 301 | 302 | dopt->min = mcp_atod(line); 303 | if (dopt->min <= 0.0) { 304 | log_stderr("mcperf: invalid minimum value '%s'", line); 305 | return MCP_ERROR; 306 | } 307 | 308 | line = pos + 1; 309 | 310 | dopt->max = mcp_atod(line); 311 | if (dopt->max <= 0.0) { 312 | log_stderr("mcperf: invalid maximum value '%s'", line); 313 | return MCP_ERROR; 314 | } 315 | if (dopt->max < dopt->min) { 316 | log_stderr("mcperf: maximum value '%g' should be greater than or " 317 | "equal to minium value '%g'", dopt->max, dopt->min); 318 | return MCP_ERROR; 319 | } 320 | 321 | break; 322 | 323 | default: 324 | NOT_REACHED(); 325 | } 326 | 327 | return MCP_OK; 328 | } 329 | 330 | static rstatus_t 331 | mcp_get_method(struct context *ctx, char *line) 332 | { 333 | struct opt *opt = &ctx->opt; 334 | size_t linelen = strlen(line); 335 | struct string *str; 336 | 337 | for (str = req_strings; str->data != NULL; str++) { 338 | if ((str->len - 1 == linelen) && 339 | strncmp(str->data, line, linelen) == 0) { 340 | opt->method = str - req_strings; 341 | return MCP_OK; 342 | } 343 | } 344 | 345 | log_stderr("mcperf: '%s' is an invalid method; valid methods are get, " 346 | "gets, delete, cas, set, add, replace, prepend, incr and " 347 | "decr", line); 348 | 349 | return MCP_ERROR; 350 | } 351 | 352 | static rstatus_t 353 | mcp_get_options(struct context *ctx, int argc, char **argv) 354 | { 355 | rstatus_t status; 356 | struct opt *opt; 357 | int c, value; 358 | size_t size; 359 | double real; 360 | char *pos; 361 | 362 | opt = &ctx->opt; 363 | opterr = 0; 364 | 365 | for (;;) { 366 | c = getopt_long(argc, argv, short_options, long_options, NULL); 367 | if (c == -1) { 368 | /* no more options */ 369 | break; 370 | } 371 | 372 | switch(c) { 373 | case 'h': 374 | show_version = 1; 375 | show_help = 1; 376 | break; 377 | 378 | case 'V': 379 | show_version = 1; 380 | break; 381 | 382 | case 'v': 383 | value = mcp_atoi(optarg); 384 | if (value < 0) { 385 | log_stderr("mcperf: option -v requires a number"); 386 | return MCP_ERROR; 387 | } 388 | opt->log_level = value; 389 | break; 390 | 391 | case 'o': 392 | opt->log_filename = optarg; 393 | break; 394 | 395 | case 's': 396 | opt->server = optarg; 397 | break; 398 | 399 | case 'p': 400 | value = mcp_atoi(optarg); 401 | if (value < 0) { 402 | log_stderr("mcperf: option -p requires a number"); 403 | return MCP_ERROR; 404 | } 405 | if (!mcp_valid_port(value)) { 406 | log_stderr("mcperf: option -p value %d is not a valid port", 407 | value); 408 | return MCP_ERROR; 409 | } 410 | 411 | opt->port = (uint16_t)value; 412 | break; 413 | 414 | case 'H': 415 | opt->print_histogram = 1; 416 | break; 417 | 418 | case 't': 419 | real = mcp_atod(optarg); 420 | if (real < 0.0) { 421 | log_stderr("mcperf: option -T requires a real number"); 422 | return MCP_ERROR; 423 | } 424 | opt->timeout = real; 425 | break; 426 | 427 | case 'l': 428 | value = mcp_atoi(optarg); 429 | if (value < 0) { 430 | log_stderr("mcperf: option -l requires a number"); 431 | return MCP_ERROR; 432 | } 433 | opt->linger = 1; 434 | opt->linger_timeout = value; 435 | break; 436 | 437 | case 'b': 438 | value = mcp_atoi(optarg); 439 | if (value < 0) { 440 | log_stderr("mcperf: option -b requires a number"); 441 | return MCP_ERROR; 442 | } 443 | opt->send_buf_size = value; 444 | break; 445 | 446 | case 'B': 447 | value = mcp_atoi(optarg); 448 | if (value < 0) { 449 | log_stderr("mcperf: option -B requires a number"); 450 | return MCP_ERROR; 451 | } 452 | opt->recv_buf_size = value; 453 | break; 454 | 455 | case 'm': 456 | status = mcp_get_method(ctx, optarg); 457 | if (status != MCP_OK) { 458 | return status; 459 | } 460 | break; 461 | 462 | case 'e': 463 | value = mcp_atoi(optarg); 464 | if (value < 0) { 465 | log_stderr("mcperf: option -e requires a number"); 466 | return MCP_ERROR; 467 | } 468 | opt->expiry = (uint32_t)value; 469 | break; 470 | 471 | case 'q': 472 | opt->use_noreply = 1; 473 | break; 474 | 475 | case 'P': 476 | size = strlen(optarg); 477 | if (size > MCP_PREFIX_LEN) { 478 | log_stderr("mcperf: key prefix cannot exceed %d in length", 479 | MCP_PREFIX_LEN); 480 | return MCP_ERROR; 481 | } 482 | opt->prefix.data = optarg; 483 | opt->prefix.len = size; 484 | break; 485 | 486 | case 'c': 487 | pos = strchr(optarg, '/'); 488 | if (pos == NULL) { 489 | log_stderr("mcperf: invalid client id format '%s'", optarg); 490 | return MCP_ERROR; 491 | } 492 | *pos = '\0'; 493 | 494 | value = mcp_atoi(optarg); 495 | if (value < 0) { 496 | log_stderr("mcperf: client id is not a number '%s'", optarg); 497 | return MCP_ERROR; 498 | } 499 | opt->client.id = (uint32_t)value; 500 | 501 | optarg = pos + 1; 502 | 503 | value = mcp_atoi(optarg); 504 | if (value < 0) { 505 | log_stderr("mcperf: number of clients is not a number '%s'", 506 | optarg); 507 | return MCP_ERROR; 508 | } 509 | opt->client.n = (uint32_t)value; 510 | break; 511 | 512 | case 'n': 513 | value = mcp_atoi(optarg); 514 | if (value < 0) { 515 | log_stderr("mcperf: option -n requires a number"); 516 | return MCP_ERROR; 517 | } 518 | opt->num_conns = (uint32_t)value; 519 | break; 520 | 521 | case 'N': 522 | value = mcp_atoi(optarg); 523 | if (value < 0) { 524 | log_stderr("mcperf: option -N requires a number"); 525 | return MCP_ERROR; 526 | } 527 | opt->num_calls = (uint32_t)value; 528 | break; 529 | 530 | case 'r': 531 | status = mcp_get_dist_opt(&opt->conn_dopt, optarg); 532 | if (status != MCP_OK) { 533 | return status; 534 | } 535 | break; 536 | 537 | case 'R': 538 | status = mcp_get_dist_opt(&opt->call_dopt, optarg); 539 | if (status != MCP_OK) { 540 | return status; 541 | } 542 | break; 543 | 544 | case 'D': 545 | opt->disable_nodelay = 1; 546 | break; 547 | 548 | case 'z': 549 | status = mcp_get_dist_opt(&opt->size_dopt, optarg); 550 | if (status != MCP_OK) { 551 | return status; 552 | } 553 | if (opt->size_dopt.type == DIST_NONE) { 554 | log_stderr("mcperf: invalid distribution type %d for item " 555 | "sizes", opt->size_dopt.type); 556 | return MCP_ERROR; 557 | } 558 | break; 559 | 560 | case '?': 561 | switch (optopt) { 562 | case 'o': 563 | log_stderr("mcperf: option -%c requires a file name", optopt); 564 | break; 565 | 566 | case 's': 567 | case 'm': 568 | case 'P': 569 | case 'c': 570 | log_stderr("mcperf: option -%c requires a string", optopt); 571 | break; 572 | 573 | case 'v': 574 | case 'p': 575 | case 'l': 576 | case 'b': 577 | case 'B': 578 | case 'e': 579 | case 'n': 580 | case 'N': 581 | log_stderr("mcperf: option -%c requires a number", optopt); 582 | break; 583 | 584 | case 't': 585 | log_stderr("mcperf: option -%c requires a real number", optopt); 586 | break; 587 | 588 | case 'r': 589 | case 'R': 590 | case 'z': 591 | log_stderr("mcperf: option -%c requires a distribution", optopt); 592 | break; 593 | 594 | default: 595 | log_stderr("mcperf: invalid option -- '%c'", optopt); 596 | break; 597 | } 598 | return MCP_ERROR; 599 | 600 | default: 601 | log_stderr("mcperf: invalid option -- '%c'", optopt); 602 | return MCP_ERROR; 603 | } 604 | } 605 | 606 | return MCP_OK; 607 | } 608 | 609 | static rstatus_t 610 | mcp_pre_run(struct context *ctx) 611 | { 612 | rstatus_t status; 613 | struct opt *opt = &ctx->opt; 614 | 615 | /* initialize logger */ 616 | status = log_init(opt->log_level, opt->log_filename); 617 | if (status != MCP_OK) { 618 | return status; 619 | } 620 | 621 | /* initialize buffer */ 622 | memset(ctx->buf1m, '0', sizeof(ctx->buf1m)); 623 | 624 | /* resolve server info */ 625 | status = mcp_resolve_addr(opt->server, opt->port, &opt->si); 626 | if (status != MCP_OK) { 627 | return status; 628 | } 629 | 630 | /* 631 | * Initialize distribution for {conn, call, size} load generators with 632 | * either default or user-supplied values. 633 | */ 634 | dist_init(&ctx->conn_dist, opt->conn_dopt.type, opt->conn_dopt.min, 635 | opt->conn_dopt.max, opt->client.id); 636 | 637 | dist_init(&ctx->call_dist, opt->call_dopt.type, opt->call_dopt.min, 638 | opt->call_dopt.max, opt->client.id); 639 | 640 | dist_init(&ctx->size_dist, opt->size_dopt.type, opt->size_dopt.min, 641 | opt->size_dopt.max, opt->client.id); 642 | 643 | /* initialize stats subsystem */ 644 | stats_init(ctx); 645 | 646 | /* initialize timer */ 647 | timer_init(); 648 | 649 | /* initialize core */ 650 | status = core_init(ctx); 651 | if (status != MCP_OK) { 652 | return status; 653 | } 654 | 655 | return MCP_OK; 656 | } 657 | 658 | static void 659 | mcp_run(struct context *ctx) 660 | { 661 | rstatus_t status; 662 | 663 | core_start(ctx); 664 | 665 | for (;;) { 666 | status = core_loop(ctx); 667 | if (status != MCP_OK) { 668 | break; 669 | } 670 | } 671 | } 672 | 673 | int 674 | main(int argc, char **argv) 675 | { 676 | rstatus_t status; 677 | static struct context ctx; 678 | 679 | mcp_set_default_options(&ctx); 680 | 681 | status = mcp_get_options(&ctx, argc, argv); 682 | if (status != MCP_OK) { 683 | mcp_show_usage(); 684 | exit(1); 685 | } 686 | 687 | if (show_version) { 688 | log_stderr("This is mcperf-%s" CRLF, MCP_VERSION_STRING); 689 | if (show_help) { 690 | mcp_show_usage(); 691 | } 692 | exit(0); 693 | } 694 | 695 | status = mcp_pre_run(&ctx); 696 | if (status != MCP_OK) { 697 | exit(1); 698 | } 699 | 700 | mcp_run(&ctx); 701 | 702 | return 0; 703 | } 704 | -------------------------------------------------------------------------------- /src/mcp_call.c: -------------------------------------------------------------------------------- 1 | /* 2 | * twemperf - a tool for measuring memcached server performance. 3 | * Copyright (C) 2011 Twitter, Inc. 4 | * 5 | * This program is free software: you can redistribute it and/or modify 6 | * it under the terms of the GNU General Public License as published by 7 | * the Free Software Foundation, either version 3 of the License, or 8 | * (at your option) any later version. 9 | * 10 | * This program is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | * GNU General Public License for more details. 14 | * 15 | * You should have received a copy of the GNU General Public License 16 | * along with this program. If not, see . 17 | */ 18 | 19 | #include 20 | 21 | static int nfree_callq; /* # free call q */ 22 | static struct call_tqh free_callq; /* free call q */ 23 | static uint64_t id; /* call id counter */ 24 | 25 | #define DEFINE_ACTION(_type, _name) { _name, sizeof(_name) - 1 }, 26 | struct string req_strings[] = { 27 | REQ_CODEC( DEFINE_ACTION ) 28 | { NULL, 0 } 29 | }; 30 | #undef DEFINE_ACTION 31 | 32 | #define DEFINE_ACTION(_type, _name) { _name, sizeof(_name) - 1 }, 33 | struct string rsp_strings[] = { 34 | RSP_CODEC( DEFINE_ACTION ) 35 | { NULL, 0 } 36 | }; 37 | #undef DEFINE_ACTION 38 | 39 | #define DEFINE_ACTION(_type, _name) { _name, sizeof(_name) - 1 }, 40 | struct string msg_strings[] = { 41 | REQ_CODEC( DEFINE_ACTION ) 42 | RSP_CODEC( DEFINE_ACTION ) 43 | MSG_CODEC( DEFINE_ACTION ) 44 | { NULL, 0 } 45 | }; 46 | #undef DEFINE_ACTION 47 | 48 | struct call * 49 | call_get(struct conn *conn) 50 | { 51 | struct call *call; 52 | uint32_t i; 53 | 54 | if (!STAILQ_EMPTY(&free_callq)) { 55 | ASSERT(nfree_callq > 0); 56 | 57 | call = STAILQ_FIRST(&free_callq); 58 | nfree_callq--; 59 | 60 | STAILQ_REMOVE_HEAD(&free_callq, call_tqe); 61 | } else { 62 | call = mcp_alloc(sizeof(*call)); 63 | if (call == NULL) { 64 | return NULL; 65 | } 66 | } 67 | 68 | STAILQ_NEXT(call, call_tqe) = NULL; 69 | call->id = ++id; 70 | call->conn = conn; 71 | 72 | /* keyname, expiry and keylen are initialized later */ 73 | call->req.send = 0; 74 | call->req.sent = 0; 75 | call->req.issue_start = 0.0; 76 | call->req.send_start = 0.0; 77 | call->req.send_stop = 0.0; 78 | for (i = 0; i < REQ_IOV_LEN; i++) { 79 | call->req.iov[i].iov_base = NULL; 80 | call->req.iov[i].iov_len = 0; 81 | } 82 | call->req.noreply = 0; 83 | call->req.sending = 0; 84 | 85 | call->rsp.recv_start = 0.0; 86 | call->rsp.rcvd = 0; 87 | call->rsp.rcurr = conn->buf; 88 | call->rsp.rsize = sizeof(conn->buf); 89 | call->rsp.pcurr = call->rsp.rcurr; 90 | call->rsp.start = NULL; 91 | call->rsp.end = NULL; 92 | call->rsp.type = 0; 93 | call->rsp.vlen = 0; 94 | call->rsp.parsed_line = 0; 95 | call->rsp.parsed_vlen = 0; 96 | 97 | log_debug(LOG_VVERB, "get call %p id %"PRIu64"", call, call->id); 98 | 99 | return call; 100 | } 101 | 102 | void 103 | call_put(struct call *call) 104 | { 105 | log_debug(LOG_VVERB, "put call %p id %"PRIu64"", call, call->id); 106 | 107 | nfree_callq++; 108 | STAILQ_INSERT_TAIL(&free_callq, call, call_tqe); 109 | } 110 | 111 | static void 112 | call_free(struct call *call) 113 | { 114 | log_debug(LOG_VVERB, "free call %p id %"PRIu64"", call, call->id); 115 | mcp_free(call); 116 | } 117 | 118 | void 119 | call_init(void) 120 | { 121 | nfree_callq = 0; 122 | STAILQ_INIT(&free_callq); 123 | } 124 | 125 | void 126 | call_deinit(void) 127 | { 128 | struct call *call, *ncall; /* current and next call */ 129 | 130 | for (call = STAILQ_FIRST(&free_callq); call != NULL; 131 | call = ncall, nfree_callq--) { 132 | ASSERT(nfree_callq > 0); 133 | ncall = STAILQ_NEXT(call, call_tqe); 134 | call_free(call); 135 | } 136 | ASSERT(nfree_callq == 0); 137 | } 138 | 139 | static rstatus_t 140 | call_start_timer(struct context *ctx, struct call *call) 141 | { 142 | struct opt *opt = &ctx->opt; 143 | struct conn *conn = call->conn; 144 | double timeout; 145 | 146 | ASSERT(!STAILQ_EMPTY(&conn->call_recvq)); 147 | 148 | if (opt->timeout == 0.0) { 149 | return MCP_OK; 150 | } 151 | 152 | if (call != STAILQ_FIRST(&conn->call_recvq)) { 153 | /* 154 | * Watcdog timer has already been scheduled by a previous call 155 | * which is still outstanding on this connection. 156 | */ 157 | ASSERT(conn->watchdog != NULL); 158 | return MCP_OK; 159 | } 160 | 161 | ASSERT(conn->watchdog == NULL); 162 | ASSERT(call->req.send_stop > 0.0); 163 | ASSERT(timer_now() >= call->req.send_stop); 164 | ASSERT(opt->timeout > (timer_now() - call->req.send_stop)); 165 | 166 | timeout = opt->timeout; 167 | timeout -= timer_now() - call->req.send_stop; 168 | conn->watchdog = timer_schedule(core_timeout, conn, timeout); 169 | if (conn->watchdog == NULL) { 170 | return MCP_ENOMEM; 171 | } 172 | 173 | return MCP_OK; 174 | } 175 | 176 | static rstatus_t 177 | call_reset_timer(struct context *ctx, struct call *call) 178 | { 179 | struct opt *opt = &ctx->opt; 180 | struct conn *conn = call->conn; 181 | 182 | if (opt->timeout == 0.0) { 183 | return MCP_OK; 184 | } 185 | 186 | ASSERT(conn->watchdog != NULL); 187 | timer_cancel(conn->watchdog); 188 | 189 | if (STAILQ_EMPTY(&conn->call_recvq)) { 190 | /* 191 | * Skip scheduling a timer as there are no outstanding calls 192 | * on this connection 193 | */ 194 | return MCP_OK; 195 | } 196 | 197 | return call_start_timer(ctx, STAILQ_FIRST(&conn->call_recvq)); 198 | } 199 | 200 | static void 201 | call_make_retrieval_req(struct context *ctx, struct call *call, 202 | uint32_t key_id) 203 | { 204 | struct opt *opt = &ctx->opt; 205 | int len; 206 | uint32_t i; 207 | 208 | /* retrieval request are never a noreply */ 209 | call->req.noreply = 0; 210 | 211 | for (i = 0; i < REQ_IOV_LEN; i++) { 212 | struct iovec *iov = &call->req.iov[i]; 213 | 214 | switch (i) { 215 | case REQ_IOV_METHOD: 216 | iov->iov_base = req_strings[opt->method].data; 217 | iov->iov_len = req_strings[opt->method].len; 218 | break; 219 | 220 | case REQ_IOV_KEY: 221 | len = mcp_scnprintf(call->req.keyname, sizeof(call->req.keyname), 222 | "%.*s%08"PRIx32, opt->prefix.len, 223 | opt->prefix.data, key_id); 224 | iov->iov_base = call->req.keyname; 225 | iov->iov_len = (size_t)len; 226 | break; 227 | 228 | case REQ_IOV_CRLF: 229 | iov->iov_base = msg_strings[MSG_CRLF].data; 230 | iov->iov_len = msg_strings[MSG_CRLF].len; 231 | break; 232 | 233 | case REQ_IOV_FLAG: 234 | case REQ_IOV_EXPIRY: 235 | case REQ_IOV_VLEN: 236 | case REQ_IOV_CAS: 237 | case REQ_IOV_NOREPLY: 238 | case REQ_IOV_VALUE: 239 | case REQ_IOV_CRLF2: 240 | iov->iov_base = NULL; 241 | iov->iov_len = 0; 242 | break; 243 | 244 | default: 245 | NOT_REACHED(); 246 | } 247 | call->req.send += iov->iov_len; 248 | } 249 | } 250 | 251 | static void 252 | call_make_delete_req(struct context *ctx, struct call *call, uint32_t key_id) 253 | { 254 | struct opt *opt = &ctx->opt; 255 | int len; 256 | uint32_t i; 257 | 258 | for (i = 0; i < REQ_IOV_LEN; i++) { 259 | struct iovec *iov = &call->req.iov[i]; 260 | 261 | switch (i) { 262 | case REQ_IOV_METHOD: 263 | iov->iov_base = req_strings[opt->method].data; 264 | iov->iov_len = req_strings[opt->method].len; 265 | break; 266 | 267 | case REQ_IOV_KEY: 268 | len = mcp_scnprintf(call->req.keyname, sizeof(call->req.keyname), 269 | "%.*s%08"PRIx32" ", opt->prefix.len, 270 | opt->prefix.data, key_id); 271 | iov->iov_base = call->req.keyname; 272 | iov->iov_len = (size_t)len; 273 | break; 274 | 275 | case REQ_IOV_FLAG: 276 | case REQ_IOV_EXPIRY: 277 | case REQ_IOV_VLEN: 278 | case REQ_IOV_CAS: 279 | iov->iov_base = NULL; 280 | iov->iov_len = 0; 281 | break; 282 | 283 | case REQ_IOV_NOREPLY: 284 | if (opt->use_noreply) { 285 | iov->iov_base = msg_strings[MSG_NOREPLY].data; 286 | iov->iov_len = msg_strings[MSG_NOREPLY].len; 287 | call->req.noreply = 1; 288 | } else { 289 | iov->iov_base = NULL; 290 | iov->iov_len = 0; 291 | call->req.noreply = 0; 292 | } 293 | break; 294 | 295 | case REQ_IOV_CRLF: 296 | iov->iov_base = msg_strings[MSG_CRLF].data; 297 | iov->iov_len = msg_strings[MSG_CRLF].len; 298 | break; 299 | 300 | case REQ_IOV_VALUE: 301 | case REQ_IOV_CRLF2: 302 | iov->iov_base = NULL; 303 | iov->iov_len = 0; 304 | break; 305 | 306 | default: 307 | NOT_REACHED(); 308 | } 309 | call->req.send += iov->iov_len; 310 | } 311 | } 312 | 313 | static void 314 | call_make_storage_req(struct context *ctx, struct call *call, uint32_t key_id, 315 | long int key_vlen) 316 | { 317 | struct opt *opt = &ctx->opt; 318 | int len; 319 | uint32_t i; 320 | 321 | for (i = 0; i < REQ_IOV_LEN; i++) { 322 | struct iovec *iov = &call->req.iov[i]; 323 | 324 | switch (i) { 325 | case REQ_IOV_METHOD: 326 | iov->iov_base = req_strings[opt->method].data; 327 | iov->iov_len = req_strings[opt->method].len; 328 | break; 329 | 330 | case REQ_IOV_KEY: 331 | len = mcp_scnprintf(call->req.keyname, sizeof(call->req.keyname), 332 | "%.*s%08"PRIx32" ", opt->prefix.len, 333 | opt->prefix.data, key_id); 334 | iov->iov_base = call->req.keyname; 335 | iov->iov_len = (size_t)len; 336 | break; 337 | 338 | case REQ_IOV_FLAG: 339 | iov->iov_base = msg_strings[MSG_ZERO].data; 340 | iov->iov_len = msg_strings[MSG_ZERO].len; 341 | break; 342 | 343 | case REQ_IOV_EXPIRY: 344 | len = mcp_scnprintf(call->req.expiry, sizeof(call->req.expiry), 345 | "%"PRIu32" ", opt->expiry); 346 | iov->iov_base = call->req.expiry; 347 | iov->iov_len = (size_t)len; 348 | break; 349 | 350 | case REQ_IOV_VLEN: 351 | len = mcp_scnprintf(call->req.keylen, sizeof(call->req.keylen), 352 | "%ld ", key_vlen); 353 | iov->iov_base = call->req.keylen; 354 | iov->iov_len = (size_t)len; 355 | break; 356 | 357 | case REQ_IOV_CAS: 358 | if (opt->method == REQ_CAS) { 359 | iov->iov_base = "1 "; 360 | iov->iov_len = 2; 361 | } else { 362 | iov->iov_base = NULL; 363 | iov->iov_len = 0; 364 | } 365 | break; 366 | 367 | case REQ_IOV_NOREPLY: 368 | if (opt->use_noreply) { 369 | iov->iov_base = msg_strings[MSG_NOREPLY].data; 370 | iov->iov_len = msg_strings[MSG_NOREPLY].len; 371 | call->req.noreply = 1; 372 | } else { 373 | iov->iov_base = NULL; 374 | iov->iov_len = 0; 375 | call->req.noreply = 0; 376 | } 377 | break; 378 | 379 | case REQ_IOV_CRLF: 380 | iov->iov_base = msg_strings[MSG_CRLF].data; 381 | iov->iov_len = msg_strings[MSG_CRLF].len; 382 | break; 383 | 384 | case REQ_IOV_VALUE: 385 | ASSERT(key_vlen >= 0 && key_vlen <= sizeof(ctx->buf1m)); 386 | iov->iov_base = ctx->buf1m; 387 | iov->iov_len = (size_t)key_vlen; 388 | break; 389 | 390 | case REQ_IOV_CRLF2: 391 | iov->iov_base = msg_strings[MSG_CRLF].data; 392 | iov->iov_len = msg_strings[MSG_CRLF].len; 393 | break; 394 | 395 | default: 396 | NOT_REACHED(); 397 | } 398 | call->req.send += iov->iov_len; 399 | } 400 | } 401 | 402 | static void 403 | call_make_arithmetic_req(struct context *ctx, struct call *call, 404 | uint32_t key_id, long int key_vlen) 405 | { 406 | struct opt *opt = &ctx->opt; 407 | int len; 408 | uint32_t i; 409 | 410 | for (i = 0; i < REQ_IOV_LEN; i++) { 411 | struct iovec *iov = &call->req.iov[i]; 412 | 413 | switch (i) { 414 | case REQ_IOV_METHOD: 415 | iov->iov_base = req_strings[opt->method].data; 416 | iov->iov_len = req_strings[opt->method].len; 417 | break; 418 | 419 | case REQ_IOV_KEY: 420 | len = mcp_scnprintf(call->req.keyname, sizeof(call->req.keyname), 421 | "%.*s%08"PRIx32" ", opt->prefix.len, 422 | opt->prefix.data, key_id); 423 | iov->iov_base = call->req.keyname; 424 | iov->iov_len = (size_t)len; 425 | break; 426 | 427 | case REQ_IOV_FLAG: 428 | iov->iov_base = NULL; 429 | iov->iov_len = 0; 430 | break; 431 | 432 | case REQ_IOV_EXPIRY: 433 | /* use expiry iov as incr/decr value */ 434 | len = mcp_scnprintf(call->req.expiry, sizeof(call->req.expiry), 435 | "%ld ", key_vlen); 436 | iov->iov_base = call->req.expiry; 437 | iov->iov_len = (size_t)len; 438 | break; 439 | 440 | case REQ_IOV_VLEN: 441 | case REQ_IOV_CAS: 442 | iov->iov_base = NULL; 443 | iov->iov_len = 0; 444 | break; 445 | 446 | case REQ_IOV_NOREPLY: 447 | if (opt->use_noreply) { 448 | iov->iov_base = msg_strings[MSG_NOREPLY].data; 449 | iov->iov_len = msg_strings[MSG_NOREPLY].len; 450 | call->req.noreply = 1; 451 | } else { 452 | iov->iov_base = NULL; 453 | iov->iov_len = 0; 454 | call->req.noreply = 0; 455 | } 456 | break; 457 | 458 | case REQ_IOV_CRLF: 459 | iov->iov_base = msg_strings[MSG_CRLF].data; 460 | iov->iov_len = msg_strings[MSG_CRLF].len; 461 | break; 462 | 463 | case REQ_IOV_VALUE: 464 | case REQ_IOV_CRLF2: 465 | iov->iov_base = NULL; 466 | iov->iov_len = 0; 467 | break; 468 | 469 | default: 470 | NOT_REACHED(); 471 | } 472 | call->req.send += iov->iov_len; 473 | } 474 | } 475 | 476 | void 477 | call_make_req(struct context *ctx, struct call *call) 478 | { 479 | struct opt *opt = &ctx->opt; 480 | struct dist_info *di = &ctx->size_dist; 481 | uint32_t key_id; 482 | long int key_vlen; 483 | 484 | call->req.send = 0; 485 | call->req.sent = 0; 486 | 487 | /* 488 | * Get the current item id and size from the distribution, and 489 | * call into the size generator to move to the next value 490 | */ 491 | key_id = di->next_id; 492 | key_vlen = lrint(di->next_val); 493 | ecb_signal(ctx, EVENT_GEN_SIZE_FIRE, &ctx->size_gen); 494 | 495 | switch (opt->method) { 496 | case REQ_GET: 497 | case REQ_GETS: 498 | call_make_retrieval_req(ctx, call, key_id); 499 | break; 500 | 501 | case REQ_DELETE: 502 | call_make_delete_req(ctx, call, key_id); 503 | break; 504 | 505 | case REQ_CAS: 506 | case REQ_SET: 507 | case REQ_ADD: 508 | case REQ_REPLACE: 509 | case REQ_APPEND: 510 | case REQ_PREPEND: 511 | case REQ_XXX: 512 | call_make_storage_req(ctx, call, key_id, key_vlen); 513 | break; 514 | 515 | case REQ_INCR: 516 | case REQ_DECR: 517 | call_make_arithmetic_req(ctx, call, key_id, key_vlen); 518 | break; 519 | 520 | default: 521 | NOT_REACHED(); 522 | } 523 | } 524 | 525 | rstatus_t 526 | call_send(struct context *ctx, struct call *call) 527 | { 528 | struct conn *conn = call->conn; 529 | size_t sent; 530 | ssize_t n; 531 | uint32_t i; 532 | 533 | ASSERT(call->req.send != 0); 534 | 535 | if (!call->req.sending) { 536 | /* 537 | * We might need multiple write events to send a call completely. 538 | * Signal the first time we start sending a given call. 539 | */ 540 | ecb_signal(ctx, EVENT_CALL_SEND_START, call); 541 | call->req.sending = 1; 542 | } 543 | 544 | n = conn_sendv(conn, call->req.iov, REQ_IOV_LEN, call->req.send); 545 | 546 | sent = n > 0 ? (size_t)n : 0; 547 | 548 | log_debug(LOG_VERB, "send call %"PRIu64" on c %"PRIu64" sd %d %zu of %zu " 549 | "bytes", call->id, conn->id, conn->sd, sent, call->req.send); 550 | 551 | call->req.send -= sent; 552 | call->req.sent += sent; 553 | 554 | for (i = 0; i < REQ_IOV_LEN; i++) { 555 | struct iovec *iov = &call->req.iov[i]; 556 | 557 | if (sent == 0) { 558 | break; 559 | } 560 | 561 | if (sent < iov->iov_len) { 562 | /* iov element was sent partially; send remaining bytes later */ 563 | iov->iov_base = (char *)iov->iov_base + sent; 564 | iov->iov_len -= sent; 565 | sent = 0; 566 | break; 567 | } 568 | 569 | /* iov element was sent completely; mark it empty */ 570 | sent -= iov->iov_len; 571 | iov->iov_base = NULL; 572 | iov->iov_len = 0; 573 | } 574 | 575 | if (call->req.send == 0) { 576 | ecb_signal(ctx, EVENT_CALL_SEND_STOP, call); 577 | 578 | /* 579 | * Call has been sent completely; move the call from sendq 580 | * to recvq unless it has been marked as noreply. 581 | */ 582 | conn->ncall_sendq--; 583 | STAILQ_REMOVE(&conn->call_sendq, call, call, call_tqe); 584 | 585 | if (call->req.noreply) { 586 | ecb_signal(ctx, EVENT_CALL_DESTROYED, call); 587 | call_put(call); 588 | } else { 589 | STAILQ_INSERT_TAIL(&conn->call_recvq, call, call_tqe); 590 | conn->ncall_recvq++; 591 | call_start_timer(ctx, call); 592 | } 593 | } 594 | 595 | if (n > 0) { 596 | return MCP_OK; 597 | } 598 | 599 | return (n == MCP_EAGAIN) ? MCP_OK : MCP_ERROR; 600 | } 601 | 602 | static rstatus_t 603 | call_parse_rsp_line(struct context *ctx, struct call *call) 604 | { 605 | char *p, *q; 606 | int size; 607 | struct string *str; 608 | 609 | if (call->rsp.parsed_line) { 610 | return MCP_OK; 611 | } 612 | 613 | ASSERT(call->rsp.rcurr > call->rsp.pcurr); 614 | 615 | size = call->rsp.rcurr - call->rsp.pcurr; 616 | 617 | p = call->rsp.pcurr; 618 | q = mcp_memchr(p, '\n', size); 619 | if (q == NULL) { 620 | return MCP_EAGAIN; 621 | } 622 | ASSERT(*(q - 1) == '\r'); 623 | q = q + 1; 624 | 625 | /* update the start and end marker of response line */ 626 | call->rsp.start = p; 627 | call->rsp.end = q; 628 | 629 | /* update the parsing marker */ 630 | call->rsp.pcurr = q; 631 | 632 | for (str = &rsp_strings[0]; str->data != NULL; str++) { 633 | if ((str->len < (call->rsp.end - call->rsp.start)) && 634 | strncmp(p, str->data, str->len) == 0) { 635 | call->rsp.type = str - rsp_strings; 636 | call->rsp.parsed_line = 1; 637 | return MCP_OK; 638 | } 639 | } 640 | 641 | return MCP_ERROR; 642 | } 643 | 644 | static rstatus_t 645 | call_parse_rsp_vlen(struct context *ctx, struct call *call) 646 | { 647 | char *p, *q; 648 | int token; 649 | 650 | p = call->rsp.start; 651 | q = call->rsp.end; 652 | 653 | /* 654 | * Parse value line with format: 655 | * VALUE \r\n\r\n 656 | */ 657 | token = 0; 658 | while (p < q) { 659 | if (*p != ' ') { 660 | p++; 661 | continue; 662 | } 663 | 664 | token++; 665 | 666 | /* skip multiple spaces */ 667 | while (*p == ' ') { 668 | p++; 669 | } 670 | 671 | if (token == 3) { 672 | break; 673 | } 674 | } 675 | 676 | if (token != 3) { 677 | return MCP_EAGAIN; 678 | } 679 | 680 | call->rsp.vlen = 0; 681 | while (p < q) { 682 | if (*p < '0' || *p > '9') { 683 | break; 684 | } 685 | 686 | call->rsp.vlen = call->rsp.vlen * 10 + (uint32_t)(*p - '0'); 687 | p++; 688 | } 689 | 690 | p = (p != q) ? p : p - 1; 691 | q = mcp_memchr(p, '\n', q - p); 692 | if (q == NULL) { 693 | call->rsp.vlen = 0; 694 | return MCP_EAGAIN; 695 | } 696 | 697 | call->rsp.vlen += (sizeof("\r\n") - 1) + (sizeof("END\r\n") - 1); 698 | call->rsp.parsed_vlen = 1; 699 | 700 | return MCP_OK; 701 | } 702 | 703 | static rstatus_t 704 | call_parse_rsp_value(struct context *ctx, struct call *call) 705 | { 706 | rstatus_t status; 707 | struct conn *conn = call->conn; 708 | size_t size; 709 | 710 | if (!call->rsp.parsed_vlen) { 711 | status = call_parse_rsp_vlen(ctx, call); 712 | if (status != MCP_OK) { 713 | return status; 714 | } 715 | ASSERT(call->rsp.parsed_vlen); 716 | } 717 | 718 | ASSERT(call->rsp.rcurr >= call->rsp.pcurr); 719 | 720 | size = (size_t)(call->rsp.rcurr - call->rsp.pcurr); 721 | 722 | if (call->rsp.vlen < size) { 723 | /* 724 | * Unparsed data in the read buffer after vlen bytes 725 | * should be parsed as a response for the next calls 726 | */ 727 | call->rsp.pcurr = call->rsp.pcurr + call->rsp.vlen; 728 | call->rsp.vlen = 0; 729 | return MCP_OK; 730 | } 731 | 732 | call->rsp.vlen -= size; 733 | 734 | /* 735 | * We have parsed all the data in the read buffer. Reset the read 736 | * marker to make more space in the read buffer 737 | */ 738 | call->rsp.rcurr = conn->buf; 739 | call->rsp.rsize = sizeof(conn->buf); 740 | call->rsp.pcurr = call->rsp.rcurr; 741 | 742 | return call->rsp.vlen == 0 ? MCP_OK : MCP_EAGAIN; 743 | } 744 | 745 | static rstatus_t 746 | call_parse_rsp(struct context *ctx, struct call *call) 747 | { 748 | rstatus_t status; 749 | 750 | /* 751 | * Parse the response line until crlf is encountered to 752 | * determine the response type. 753 | */ 754 | status = call_parse_rsp_line(ctx, call); 755 | if (status != MCP_OK) { 756 | return status; 757 | } 758 | 759 | /* 760 | * After we have parsed the response line and determined that it is a 761 | * value response, we need to parse remaining data until we encounter 762 | * data of size value length followed by crlf. 763 | */ 764 | if (call->rsp.type == RSP_VALUE) { 765 | return call_parse_rsp_value(ctx, call); 766 | } 767 | 768 | return MCP_OK; 769 | } 770 | 771 | rstatus_t 772 | call_recv(struct context *ctx, struct call *call) 773 | { 774 | rstatus_t status; 775 | struct conn *conn = call->conn; 776 | ssize_t n; 777 | size_t rcvd; 778 | 779 | if (call->rsp.rsize == 0) { 780 | size_t chunk_size; 781 | 782 | ASSERT(call->rsp.rcurr > call->rsp.pcurr); 783 | 784 | /* 785 | * Make space in the read buffer by moving the unparsed chunk 786 | * at the tail end to the head. 787 | */ 788 | chunk_size = (size_t)(call->rsp.rcurr - call->rsp.pcurr); 789 | mcp_memmove(conn->buf, call->rsp.pcurr, chunk_size); 790 | call->rsp.pcurr = conn->buf; 791 | call->rsp.rcurr = conn->buf + chunk_size; 792 | call->rsp.rsize = sizeof(conn->buf) - chunk_size; 793 | } 794 | 795 | if (call->rsp.rcvd == 0) { 796 | ecb_signal(ctx, EVENT_CALL_RECV_START, call); 797 | } 798 | 799 | n = conn_recv(conn, call->rsp.rcurr, call->rsp.rsize); 800 | 801 | rcvd = n > 0 ? (size_t)n : 0; 802 | 803 | call->rsp.rcvd += rcvd; 804 | call->rsp.rcurr += rcvd; 805 | call->rsp.rsize -= rcvd; 806 | 807 | if (n <= 0) { 808 | if (n == 0 || n == MCP_EAGAIN) { 809 | return MCP_OK; 810 | } 811 | return MCP_ERROR; 812 | } 813 | 814 | do { 815 | struct call *next_call; /* next call in recv q */ 816 | 817 | status = call_parse_rsp(ctx, call); 818 | if (status != MCP_OK) { 819 | if (status == MCP_EAGAIN) { 820 | /* incomplete response; parse again when more data arrives */ 821 | return MCP_OK; 822 | } 823 | return status; 824 | } 825 | 826 | next_call = NULL; 827 | /* 828 | * Spill over unparsed response onto the next call and update 829 | * the current call appropriately 830 | */ 831 | if (call->rsp.rcurr != call->rsp.pcurr) { 832 | next_call = STAILQ_NEXT(call, call_tqe); 833 | if (next_call == NULL) { 834 | log_debug(LOG_ERR, "stray response type %d on c %"PRIu64"", 835 | call->rsp.type, conn->id); 836 | conn->err = EINVAL; 837 | return MCP_ERROR; 838 | } 839 | 840 | ecb_signal(ctx, EVENT_CALL_RECV_START, next_call); 841 | 842 | next_call->rsp.rcurr = call->rsp.rcurr; 843 | next_call->rsp.rsize = call->rsp.rsize; 844 | next_call->rsp.pcurr = call->rsp.pcurr; 845 | 846 | /* 847 | * Calculate the exact bytes received on this call and accumulate 848 | * the remaining bytes onto the next call 849 | */ 850 | ASSERT(call->rsp.rcurr > call->rsp.pcurr); 851 | next_call->rsp.rcvd = (size_t)(call->rsp.rcurr - call->rsp.pcurr); 852 | call->rsp.rcvd -= next_call->rsp.rcvd; 853 | } 854 | 855 | conn->ncall_recvq--; 856 | STAILQ_REMOVE(&conn->call_recvq, call, call, call_tqe); 857 | 858 | call_reset_timer(ctx, call); 859 | 860 | ecb_signal(ctx, EVENT_CALL_RECV_STOP, call); 861 | ecb_signal(ctx, EVENT_CALL_DESTROYED, call); 862 | 863 | call_put(call); 864 | 865 | call = next_call; 866 | } while (call != NULL); 867 | 868 | return MCP_OK; 869 | } 870 | --------------------------------------------------------------------------------