PageRenderTime 55ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/redis-load.c

http://github.com/antirez/redis-tools
C | 604 lines | 509 code | 64 blank | 31 comment | 170 complexity | cd3acbfd0c659a4a97ff347e8cb3316c MD5 | raw file
Possible License(s): BSD-3-Clause
  1. /* Redis load utility.
  2. *
  3. * Copyright (c) 2009-2010, Salvatore Sanfilippo <antirez at gmail dot com>
  4. * All rights reserved.
  5. *
  6. * This software is NOT released under a free software license.
  7. * It is a commercial tool, under the terms of the license you can find in
  8. * the COPYING file in the Redis-Tools distribution.
  9. */
  10. #include "fmacros.h"
  11. #include <stdio.h>
  12. #include <string.h>
  13. #include <stdlib.h>
  14. #include <unistd.h>
  15. #include <errno.h>
  16. #include <signal.h>
  17. #include <assert.h>
  18. #include <signal.h>
  19. #include <math.h>
  20. #include <limits.h>
  21. #include "hiredis.h"
  22. #include "adapters/ae.h"
  23. #include "adlist.h"
  24. #include "zmalloc.h"
  25. #include "rc4rand.h"
  26. #include "utils.h"
  27. #define REDIS_IDLE 0
  28. #define REDIS_GET 1
  29. #define REDIS_SET 2
  30. #define REDIS_DEL 3
  31. #define REDIS_SWAPIN 4
  32. #define REDIS_LPUSH 6
  33. #define REDIS_LPOP 7
  34. #define REDIS_HSET 8
  35. #define REDIS_HGET 9
  36. #define REDIS_HGETALL 10
  37. #define MAX_LATENCY 5000
  38. #define DEFAULT_KEYSPACE 100000 /* 100k */
  39. #define DEFAULT_HASHKEYSPACE 1000 /* 1k */
  40. #define REDIS_NOTUSED(V) ((void) V)
  41. static struct config {
  42. aeEventLoop *el;
  43. int debug;
  44. int done;
  45. list *clients;
  46. int num_clients;
  47. int num_requests;
  48. int issued_requests;
  49. int datasize_min;
  50. int datasize_max;
  51. unsigned char *databuf;
  52. int keyspace;
  53. int hashkeyspace;
  54. int set_perc;
  55. int del_perc;
  56. int swapin_perc;
  57. int lpush_perc;
  58. int lpop_perc;
  59. int hset_perc;
  60. int hget_perc;
  61. int hgetall_perc;
  62. int check;
  63. int rand;
  64. int longtail;
  65. int longtail_order;
  66. char *hostip;
  67. int hostport;
  68. int keepalive;
  69. long long start;
  70. long long totlatency;
  71. int *latency;
  72. int quiet;
  73. int loop;
  74. int idlemode;
  75. int ctrlc; /* Ctrl + C pressed */
  76. unsigned int prngseed;
  77. /* The following "optab" array is used in order to randomize the different
  78. * kind of operations, like GET, SET, LPUSH, LPOP, SADD, and so forth.
  79. * For every query the client will pick a random bucket from 0 to 99
  80. * and will check what is the kind of operation to perform, also it will
  81. * use the bucket ID in order to make sure this kind of operation is
  82. * always executed against the right data type, so for instance if bucket
  83. * 7 is a LPUSH the operation will be performed against key xxxxxx07
  84. * and so forth. */
  85. unsigned char optab[100];
  86. } config;
  87. typedef struct _client {
  88. redisAsyncContext *context;
  89. int state;
  90. int reqtype; /* request type. REDIS_GET, REDIS_SET, ... */
  91. long long start; /* start time in milliseconds */
  92. long keyid; /* the key name for this request is "key:<keyid>" */
  93. } *client;
  94. /* Prototypes */
  95. static void issueRequest(client c);
  96. static void createMissingClients(void);
  97. /* Return a pseudo random number between min and max both inclusive */
  98. static long randbetween(long min, long max) {
  99. return min+(random()%(max-min+1));
  100. }
  101. /* PRNG biased accordingly to the power law (Long Tail alike) */
  102. unsigned long longtailprng(unsigned long min, unsigned long max, int n) {
  103. unsigned long pl;
  104. double r = (double)(random()&(INT_MAX-1))/INT_MAX;
  105. max += 1;
  106. pl = pow((pow(max,n+1) - pow(min,n+1))*r + pow(min,n+1),1.0/(n+1));
  107. return (max-1-pl)+min;
  108. }
  109. static void clientDisconnected(const redisAsyncContext *context, int status) {
  110. listNode *ln;
  111. client c = (client)context->data;
  112. if (status != REDIS_OK) {
  113. fprintf(stderr,"Disconnected: %s\n",c->context->errstr);
  114. exit(1);
  115. }
  116. ln = listSearchKey(config.clients,c);
  117. assert(ln != NULL);
  118. listDelNode(config.clients,ln);
  119. zfree(c);
  120. /* The run was not done, create new client(s). */
  121. if (!config.done) {
  122. createMissingClients();
  123. }
  124. /* Stop the event loop when all clients were disconnected */
  125. if (!listLength(config.clients)) {
  126. aeStop(config.el);
  127. }
  128. }
  129. static client createClient(void) {
  130. client c = zmalloc(sizeof(struct _client));
  131. c->context = redisAsyncConnect(config.hostip,config.hostport);
  132. c->context->data = c;
  133. redisAsyncSetDisconnectCallback(c->context,clientDisconnected);
  134. if (c->context->err) {
  135. fprintf(stderr,"Connect: %s\n",c->context->errstr);
  136. exit(1);
  137. }
  138. redisAeAttach(config.el,c->context);
  139. listAddNodeTail(config.clients,c);
  140. issueRequest(c);
  141. return c;
  142. }
  143. static void createMissingClients(void) {
  144. while(listLength(config.clients) < (size_t)config.num_clients) {
  145. createClient();
  146. }
  147. }
  148. static void checkDataIntegrity(client c, redisReply *reply) {
  149. if (c->reqtype == REDIS_GET && reply->type == REDIS_REPLY_STRING) {
  150. unsigned char *data;
  151. unsigned int datalen;
  152. rc4rand_seed(c->keyid);
  153. datalen = rc4rand_between(config.datasize_min,config.datasize_max);
  154. data = zmalloc(datalen);
  155. rc4rand_set(data,datalen);
  156. if (reply->len != (int)datalen) {
  157. fprintf(stderr, "*** Len mismatch for KEY key:%ld\n", c->keyid);
  158. fprintf(stderr, "*** %d instead of %d\n", reply->len, datalen);
  159. fprintf(stderr, "*** '%s' instead of '%s'\n", reply->str, data);
  160. exit(1);
  161. }
  162. if (memcmp(reply->str,data,datalen) != 0) {
  163. fprintf(stderr, "*** Data mismatch for KEY key:%ld\n", c->keyid);
  164. fprintf(stderr, "*** '%s' instead of '%s'\n", reply->str, data);
  165. exit(1);
  166. }
  167. zfree(data);
  168. }
  169. }
  170. static void handleReply(redisAsyncContext *context, void *_reply, void *privdata) {
  171. REDIS_NOTUSED(privdata);
  172. redisReply *reply = (redisReply*)_reply;
  173. client c = (client)context->data;
  174. long long latency = (microseconds() - c->start) / 1000;
  175. if (reply == NULL && context->err) {
  176. fprintf(stderr,"Error: %s\n", context->errstr);
  177. exit(1);
  178. } else {
  179. assert(reply != NULL);
  180. if (reply->type == REDIS_REPLY_ERROR) {
  181. fprintf(stderr,"Error: %s\n", reply->str);
  182. exit(1);
  183. }
  184. }
  185. if (latency > MAX_LATENCY) latency = MAX_LATENCY;
  186. config.latency[latency]++;
  187. if (config.check) checkDataIntegrity(c,reply);
  188. freeReplyObject(reply);
  189. if (config.done || config.ctrlc) {
  190. redisAsyncDisconnect(c->context);
  191. return;
  192. }
  193. if (config.keepalive) {
  194. issueRequest(c);
  195. } else {
  196. /* createMissingClients will be called in the disconnection callback */
  197. redisAsyncDisconnect(c->context);
  198. }
  199. }
  200. static unsigned long randomData(long seed) {
  201. unsigned long datalen;
  202. /* We use the key number as seed of the PRNG, so we'll be able to check if
  203. * a given key contains the right data later, without the use of additional
  204. * memory. */
  205. if (config.check) {
  206. rc4rand_seed(seed);
  207. datalen = rc4rand_between(config.datasize_min,config.datasize_max);
  208. rc4rand_set(config.databuf,datalen);
  209. } else {
  210. datalen = randbetween(config.datasize_min,config.datasize_max);
  211. if (config.rand) {
  212. rc4rand_seed(seed);
  213. rc4rand_set(config.databuf,datalen);
  214. } else {
  215. memset(config.databuf,'x',datalen);
  216. }
  217. }
  218. return datalen;
  219. }
  220. static void issueRequest(client c) {
  221. int op = config.optab[random() % 100];
  222. long key, hashkey;
  223. unsigned long datalen;
  224. config.issued_requests++;
  225. if (config.issued_requests == config.num_requests) config.done = 1;
  226. c->start = microseconds();
  227. if (config.longtail) {
  228. key = longtailprng(0,config.keyspace-1,config.longtail_order);
  229. hashkey = longtailprng(0,config.hashkeyspace-1,config.longtail_order);
  230. } else {
  231. key = random() % config.keyspace;
  232. hashkey = random() % config.hashkeyspace;
  233. }
  234. c->keyid = key;
  235. c->reqtype = op;
  236. if (op == REDIS_IDLE) {
  237. /* Idle */
  238. } else if (op == REDIS_SET) {
  239. datalen = randomData(key);
  240. redisAsyncCommand(c->context,handleReply,NULL,"SET string:%ld %b",key,config.databuf,datalen);
  241. } else if (op == REDIS_GET) {
  242. redisAsyncCommand(c->context,handleReply,NULL,"GET string:%ld",key);
  243. } else if (op == REDIS_DEL) {
  244. redisAsyncCommand(c->context,handleReply,NULL,"DEL string:%ld list:%ld hash:%ld",key,key,key);
  245. } else if (op == REDIS_LPUSH) {
  246. datalen = randomData(key);
  247. redisAsyncCommand(c->context,handleReply,NULL,"LPUSH list:%ld %b",key,config.databuf,datalen);
  248. } else if (op == REDIS_LPOP) {
  249. redisAsyncCommand(c->context,handleReply,NULL,"LPOP list:%ld",key);
  250. } else if (op == REDIS_HSET) {
  251. datalen = randomData(key);
  252. redisAsyncCommand(c->context,handleReply,NULL,"HSET hash:%ld key:%ld %b",key,hashkey,config.databuf,datalen);
  253. } else if (op == REDIS_HGET) {
  254. redisAsyncCommand(c->context,handleReply,NULL,"HGET hash:%ld key:%ld",key,hashkey);
  255. } else if (op == REDIS_HGETALL) {
  256. redisAsyncCommand(c->context,handleReply,NULL,"HGETALL hash:%ld",key);
  257. } else if (op == REDIS_SWAPIN) {
  258. /* Only accepts a single argument, so for now only works with string keys. */
  259. redisAsyncCommand(c->context,handleReply,NULL,"DEBUG SWAPIN string:%ld",key);
  260. } else {
  261. assert(NULL);
  262. }
  263. }
  264. static void showLatencyReport(void) {
  265. int j, seen = 0;
  266. float perc, reqpersec;
  267. reqpersec = (float)config.issued_requests/((float)config.totlatency/1000);
  268. if (!config.quiet) {
  269. printf("====== Report ======\n");
  270. printf(" %d requests in %.3f seconds\n", config.issued_requests,
  271. (float)config.totlatency/1000);
  272. printf(" %.2f requests per second\n", reqpersec);
  273. printf(" %d parallel clients\n", config.num_clients);
  274. printf(" payload: %d..%d bytes\n", config.datasize_min, config.datasize_max);
  275. printf(" keep alive: %d\n", config.keepalive);
  276. printf("\n");
  277. for (j = 0; j <= MAX_LATENCY; j++) {
  278. if (config.latency[j]) {
  279. seen += config.latency[j];
  280. perc = ((float)seen*100)/config.issued_requests;
  281. printf("%6.2f%% < %d ms\n", perc, j+1);
  282. }
  283. }
  284. } else {
  285. printf("%.2f requests per second\n", reqpersec);
  286. }
  287. }
  288. static void prepareForBenchmark(void) {
  289. memset(config.latency,0,sizeof(int)*(MAX_LATENCY+1));
  290. config.start = microseconds();
  291. config.issued_requests = 0;
  292. }
  293. static void endBenchmark(void) {
  294. config.totlatency = (microseconds()-config.start)/1000;
  295. showLatencyReport();
  296. }
  297. static void usage(char *wrong) {
  298. if (wrong)
  299. printf("Wrong option '%s' or option argument missing\n\n",wrong);
  300. printf(
  301. "Usage: redis-load ... options ...\n\n"
  302. " host <hostname> Server hostname (default 127.0.0.1)\n"
  303. " port <hostname> Server port (default 6379)\n"
  304. " clients <clients> Number of parallel connections (default 50)\n"
  305. " requests <requests> Total number of requests (default 10k)\n"
  306. " mindatasize <size> Min data size of string values in bytes (default 1)\n"
  307. " maxdatasize <size> Min data size of string values in bytes (default 64)\n"
  308. " datasize <size> Set both min and max data size to the same value\n"
  309. " keepalive 1=keep alive 0=reconnect (default 1)\n"
  310. " keyspace The number of different keys to use (default 100k)\n"
  311. " rand Use random data payload (incompressible)\n"
  312. " check Check integrity where reading data back (implies rand)\n"
  313. " longtail Use long tail alike key access pattern distribution\n"
  314. " longtailorder A value of 2: 20%% keys get 49%% accesses.\n"
  315. " 3: 20%% keys get 59%% accesses.\n"
  316. " 4: 20%% keys get 67%% accesses.\n"
  317. " 5: 20%% keys get 74%% accesses.\n"
  318. " 6: 20%% keys get 79%% accesses (default).\n"
  319. " 7: 20%% keys get 83%% accesses.\n"
  320. " 8: 20%% keys get 86%% accesses.\n"
  321. " 9: 20%% keys get 89%% accesses.\n"
  322. " 10: 20%% keys get 91%% accesses.\n"
  323. " 20: 20%% keys get 99%% accesses.\n"
  324. " seed <seed> PRNG seed for deterministic load\n"
  325. " big alias for keyspace 1000000 requests 1000000\n"
  326. " verybig alias for keyspace 10000000 requests 10000000\n"
  327. " quiet Quiet mode, less verbose\n"
  328. " loop Loop. Run the tests forever\n"
  329. " idle Idle mode. Just open N idle connections and wait.\n"
  330. " debug Debug mode. more verbose.\n"
  331. "\n"
  332. "Type of operations (use percentages without trailing %%):\n"
  333. "\n"
  334. " set <percentage> Percentage of SETs (default 50)\n"
  335. " del <percentage> Percentage of DELs (default 0)\n"
  336. " lpush <percentage> Percentage of LPUSHs (default 0)\n"
  337. " lpop <percentage> Percentage of LPOPs (default 0)\n"
  338. " hset <percentage> Percentage of HSETs (default 0)\n"
  339. " hget <percentage> Percentage of HGETs (default 0)\n"
  340. " hgetall <percentage> Percentage of HGETs (default 0)\n"
  341. " swapin <percentage> Percentage of DEBUG SWAPINs (default 0)\n"
  342. "\n"
  343. " All the free percantege (in order to reach 100%%) will be used for GETs\n"
  344. );
  345. exit(1);
  346. }
  347. static void parseOptions(int argc, char **argv) {
  348. int i;
  349. for (i = 1; i < argc; i++) {
  350. int lastarg = i==argc-1;
  351. if (!strcmp(argv[i],"clients") && !lastarg) {
  352. config.num_clients = atoi(argv[i+1]);
  353. i++;
  354. } else if (!strcmp(argv[i],"requests") && !lastarg) {
  355. config.num_requests = atoi(argv[i+1]);
  356. i++;
  357. } else if (!strcmp(argv[i],"set") && !lastarg) {
  358. config.set_perc = atoi(argv[i+1]);
  359. i++;
  360. } else if (!strcmp(argv[i],"del") && !lastarg) {
  361. config.del_perc = atoi(argv[i+1]);
  362. i++;
  363. } else if (!strcmp(argv[i],"swapin") && !lastarg) {
  364. config.swapin_perc = atoi(argv[i+1]);
  365. i++;
  366. } else if (!strcmp(argv[i],"lpush") && !lastarg) {
  367. config.lpush_perc = atoi(argv[i+1]);
  368. i++;
  369. } else if (!strcmp(argv[i],"lpop") && !lastarg) {
  370. config.lpop_perc = atoi(argv[i+1]);
  371. i++;
  372. } else if (!strcmp(argv[i],"hset") && !lastarg) {
  373. config.hset_perc = atoi(argv[i+1]);
  374. i++;
  375. } else if (!strcmp(argv[i],"hget") && !lastarg) {
  376. config.hget_perc = atoi(argv[i+1]);
  377. i++;
  378. } else if (!strcmp(argv[i],"hgetall") && !lastarg) {
  379. config.hgetall_perc = atoi(argv[i+1]);
  380. i++;
  381. } else if (!strcmp(argv[i],"keepalive") && !lastarg) {
  382. config.keepalive = atoi(argv[i+1]);
  383. i++;
  384. } else if (!strcmp(argv[i],"host") && !lastarg) {
  385. config.hostip = argv[i+1];
  386. i++;
  387. } else if (!strcmp(argv[i],"port") && !lastarg) {
  388. config.hostport = atoi(argv[i+1]);
  389. i++;
  390. } else if (!strcmp(argv[i],"datasize") && !lastarg) {
  391. config.datasize_max = config.datasize_min = atoi(argv[i+1]);
  392. i++;
  393. } else if (!strcmp(argv[i],"mindatasize") && !lastarg) {
  394. config.datasize_min = atoi(argv[i+1]);
  395. i++;
  396. } else if (!strcmp(argv[i],"maxdatasize") && !lastarg) {
  397. config.datasize_max = atoi(argv[i+1]);
  398. i++;
  399. } else if (!strcmp(argv[i],"keyspace") && !lastarg) {
  400. config.keyspace = atoi(argv[i+1]);
  401. i++;
  402. } else if (!strcmp(argv[i],"hashkeyspace") && !lastarg) {
  403. config.hashkeyspace = atoi(argv[i+1]);
  404. i++;
  405. } else if (!strcmp(argv[i],"seed") && !lastarg) {
  406. config.prngseed = strtol(argv[i+1],NULL,10);
  407. i++;
  408. } else if (!strcmp(argv[i],"big")) {
  409. config.keyspace = 1000000;
  410. config.num_requests = 1000000;
  411. } else if (!strcmp(argv[i],"verybig")) {
  412. config.keyspace = 10000000;
  413. config.num_requests = 10000000;
  414. } else if (!strcmp(argv[i],"quiet")) {
  415. config.quiet = 1;
  416. } else if (!strcmp(argv[i],"check")) {
  417. config.check = 1;
  418. } else if (!strcmp(argv[i],"rand")) {
  419. config.rand = 1;
  420. } else if (!strcmp(argv[i],"longtail")) {
  421. config.longtail = 1;
  422. } else if (!strcmp(argv[i],"longtailorder") && !lastarg) {
  423. config.longtail_order = atoi(argv[i+1]);
  424. i++;
  425. if (config.longtail_order < 2 || config.longtail_order > 100) {
  426. printf("Value out of range for 'longtailorder' option");
  427. exit(1);
  428. }
  429. } else if (!strcmp(argv[i],"loop")) {
  430. config.loop = 1;
  431. } else if (!strcmp(argv[i],"debug")) {
  432. config.debug = 1;
  433. } else if (!strcmp(argv[i],"idle")) {
  434. config.idlemode = 1;
  435. } else if (!strcmp(argv[i],"help")) {
  436. usage(NULL);
  437. } else {
  438. usage(argv[i]);
  439. }
  440. }
  441. /* Sanitize options */
  442. if (config.datasize_min < 1) config.datasize_min = 1;
  443. if (config.datasize_min > 1024*1024) config.datasize_min = 1024*1024;
  444. if (config.datasize_max < 1) config.datasize_max = 1;
  445. if (config.datasize_max > 1024*1024) config.datasize_max = 1024*1024;
  446. if (config.keyspace < 1) config.keyspace = DEFAULT_KEYSPACE;
  447. if (config.hashkeyspace < 1) config.hashkeyspace = DEFAULT_HASHKEYSPACE;
  448. }
  449. static void ctrlc(int sig) {
  450. REDIS_NOTUSED(sig);
  451. if (config.idlemode) {
  452. exit(1);
  453. } else {
  454. config.ctrlc++;
  455. if (config.ctrlc == 1) {
  456. config.done = 1;
  457. printf("\nWaiting for pending requests to complete...\n");
  458. } else {
  459. printf("\nForcing exit...\n");
  460. exit(1);
  461. }
  462. }
  463. }
  464. static void fillOpTab(int *i, int op, int perc) {
  465. int j;
  466. for (j = 0; j < perc; j++) {
  467. if (*i < 100) {
  468. config.optab[*i] = op;
  469. (*i)++;
  470. }
  471. }
  472. }
  473. int main(int argc, char **argv) {
  474. int i = 0;
  475. signal(SIGHUP, SIG_IGN);
  476. signal(SIGPIPE, SIG_IGN);
  477. config.el = aeCreateEventLoop();
  478. config.debug = 0;
  479. config.done = 0;
  480. config.clients = listCreate();
  481. config.num_clients = 50;
  482. config.num_requests = 10000;
  483. config.issued_requests = 0;
  484. config.keepalive = 1;
  485. config.set_perc = 50;
  486. config.del_perc = 0;
  487. config.swapin_perc = 0;
  488. config.lpush_perc = 0;
  489. config.lpop_perc = 0;
  490. config.hset_perc = 0;
  491. config.hget_perc = 0;
  492. config.hgetall_perc = 0;
  493. config.datasize_min = 1;
  494. config.datasize_max = 64;
  495. config.keyspace = DEFAULT_KEYSPACE; /* 100k */
  496. config.hashkeyspace = DEFAULT_HASHKEYSPACE; /* 1k */
  497. config.check = 0;
  498. config.rand = 0;
  499. config.longtail = 0;
  500. config.quiet = 0;
  501. config.loop = 0;
  502. config.idlemode = 0;
  503. config.latency = NULL;
  504. config.latency = zmalloc(sizeof(int)*(MAX_LATENCY+1));
  505. config.ctrlc = 0;
  506. config.prngseed = (unsigned int) (microseconds()^getpid());
  507. config.hostip = "127.0.0.1";
  508. config.hostport = 6379;
  509. parseOptions(argc,argv);
  510. config.databuf = zmalloc(config.datasize_max);
  511. if (config.keepalive == 0) {
  512. printf("WARNING: keepalive disabled, you probably need 'echo 1 > /proc/sys/net/ipv4/tcp_tw_reuse' for Linux and 'sudo sysctl -w net.inet.tcp.msl=1000' for Mac OS X in order to use a lot of clients/requests\n");
  513. }
  514. if (config.idlemode) {
  515. printf("Creating %d idle connections and waiting forever (Ctrl+C when done)\n", config.num_clients);
  516. memset(config.optab,REDIS_IDLE,100);
  517. } else {
  518. /* Setup the operation table. Start with a table with just GET
  519. * operations and overwrite it with others as needed. */
  520. memset(config.optab,REDIS_GET,100);
  521. fillOpTab(&i,REDIS_SET,config.set_perc);
  522. fillOpTab(&i,REDIS_DEL,config.del_perc);
  523. fillOpTab(&i,REDIS_LPUSH,config.lpush_perc);
  524. fillOpTab(&i,REDIS_LPOP,config.lpop_perc);
  525. fillOpTab(&i,REDIS_HSET,config.hset_perc);
  526. fillOpTab(&i,REDIS_HGET,config.hget_perc);
  527. fillOpTab(&i,REDIS_HGETALL,config.hgetall_perc);
  528. fillOpTab(&i,REDIS_SWAPIN,config.swapin_perc);
  529. }
  530. signal(SIGINT,ctrlc);
  531. srandom(config.prngseed);
  532. printf("PRNG seed is: %u - use the 'seed' option to reproduce the same sequence\n", config.prngseed);
  533. do {
  534. prepareForBenchmark();
  535. createMissingClients();
  536. aeMain(config.el);
  537. endBenchmark();
  538. } while(config.loop);
  539. return 0;
  540. }