Commit ed9b544e authored by antirez's avatar antirez
Browse files

first commit

parents
Hello betatester!
This Redis Server distribution is just a preview, it is by no mean an usable
product, but probably it can already give you some feeling about what the
final release is going to be.
Be aware that if you want to use Redis in production the server may not be perfectly stable or may cotanin unfixed bugs. We did our best to ensure this distribution is of good quality and bug free but the development is currently very fast.
Please send feedbacks to antirez at gmail dot com.
Enjoy,
antirez
Plese check http://code.google.com/p/redis/issues/list
Copyright (c) 2006-2009, Salvatore Sanfilippo
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of Redis nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# Redis Makefile
# Copyright (C) 2009 Salvatore Sanfilippo <antirez at gmail dot com>
# This file is released under the BSD license, see the COPYING file
DEBUG?= -g
CFLAGS?= -O2 -Wall -W -DSDS_ABORT_ON_OOM
CCOPT= $(CFLAGS)
OBJ = adlist.o ae.o anet.o dict.o redis.o sds.o zmalloc.o
BENCHOBJ = ae.o anet.o benchmark.o sds.o adlist.o zmalloc.o
CLIOBJ = anet.o sds.o adlist.o redis-cli.o zmalloc.o
PRGNAME = redis-server
BENCHPRGNAME = redis-benchmark
CLIPRGNAME = redis-cli
all: redis-server redis-benchmark redis-cli
# Deps (use make dep to generate this)
adlist.o: adlist.c adlist.h
ae.o: ae.c ae.h
anet.o: anet.c anet.h
benchmark.o: benchmark.c ae.h anet.h sds.h adlist.h
dict.o: dict.c dict.h
redis-cli.o: redis-cli.c anet.h sds.h adlist.h
redis.o: redis.c ae.h sds.h anet.h dict.h adlist.h
sds.o: sds.c sds.h
sha1.o: sha1.c sha1.h
zmalloc.o: zmalloc.c
redis-server: $(OBJ)
$(CC) -o $(PRGNAME) $(CCOPT) $(DEBUG) $(OBJ)
@echo ""
@echo "Hint: To run the test-redis.tcl script is a good idea."
@echo "Launch the redis server with ./redis-server, then in another"
@echo "terminal window enter this directory and run 'make test'."
@echo ""
redis-benchmark: $(BENCHOBJ)
$(CC) -o $(BENCHPRGNAME) $(CCOPT) $(DEBUG) $(BENCHOBJ)
redis-cli: $(CLIOBJ)
$(CC) -o $(CLIPRGNAME) $(CCOPT) $(DEBUG) $(CLIOBJ)
.c.o:
$(CC) -c $(CCOPT) $(DEBUG) $(COMPILE_TIME) $<
clean:
rm -rf $(PRGNAME) $(BENCHPRGNAME) $(CLIPRGNAME) *.o
dep:
$(CC) -MM *.c
test:
tclsh test-redis.tcl
bench:
./redis-benchmark
Check the 'doc' directory. doc/README.html is a good starting point :)
BETA 8 TODO
- keys expire
- sunion ssub
- write integers in a special way on disk (and on memory?)
- compact types for disk storing of short strings (no 4 bytes overhead!)
- network layer stresser in test in demo
- maxclients directive
- check 'server.dirty' everywere
- replication tests
- command line client. If the last argument of a bulk command is missing get it from stdin. Example:
$ echo "bar" | redis-client SET foo
$ redis-client SET foo bar
$ redis-client GET foo
bar
$
- Make Redis aware of the memory it is using thanks to getrusage() and report this info with the INFO command.
- INFO command: clients, slave/master, requests/second in the last N seconds, memory usage, uptime, dirty, lastsave
/* adlist.c - A generic doubly linked list implementation
*
* Copyright (c) 2006-2009, Salvatore Sanfilippo <antirez at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <stdlib.h>
#include "adlist.h"
#include "zmalloc.h"
/* Create a new list. The created list can be freed with
* AlFreeList(), but private value of every node need to be freed
* by the user before to call AlFreeList().
*
* On error, NULL is returned. Otherwise the pointer to the new list. */
list *listCreate(void)
{
struct list *list;
if ((list = zmalloc(sizeof(*list))) == NULL)
return NULL;
list->head = list->tail = NULL;
list->len = 0;
list->dup = NULL;
list->free = NULL;
list->match = NULL;
return list;
}
/* Free the whole list.
*
* This function can't fail. */
void listRelease(list *list)
{
unsigned int len;
listNode *current, *next;
current = list->head;
len = list->len;
while(len--) {
next = current->next;
if (list->free) list->free(current->value);
zfree(current);
current = next;
}
zfree(list);
}
/* Add a new node to the list, to head, contaning the specified 'value'
* pointer as value.
*
* On error, NULL is returned and no operation is performed (i.e. the
* list remains unaltered).
* On success the 'list' pointer you pass to the function is returned. */
list *listAddNodeHead(list *list, void *value)
{
listNode *node;
if ((node = zmalloc(sizeof(*node))) == NULL)
return NULL;
node->value = value;
if (list->len == 0) {
list->head = list->tail = node;
node->prev = node->next = NULL;
} else {
node->prev = NULL;
node->next = list->head;
list->head->prev = node;
list->head = node;
}
list->len++;
return list;
}
/* Add a new node to the list, to tail, contaning the specified 'value'
* pointer as value.
*
* On error, NULL is returned and no operation is performed (i.e. the
* list remains unaltered).
* On success the 'list' pointer you pass to the function is returned. */
list *listAddNodeTail(list *list, void *value)
{
listNode *node;
if ((node = zmalloc(sizeof(*node))) == NULL)
return NULL;
node->value = value;
if (list->len == 0) {
list->head = list->tail = node;
node->prev = node->next = NULL;
} else {
node->prev = list->tail;
node->next = NULL;
list->tail->next = node;
list->tail = node;
}
list->len++;
return list;
}
/* Remove the specified node from the specified list.
* It's up to the caller to free the private value of the node.
*
* This function can't fail. */
void listDelNode(list *list, listNode *node)
{
if (node->prev)
node->prev->next = node->next;
else
list->head = node->next;
if (node->next)
node->next->prev = node->prev;
else
list->tail = node->prev;
if (list->free) list->free(node->value);
zfree(node);
list->len--;
}
/* Returns a list iterator 'iter'. After the initialization every
* call to listNextElement() will return the next element of the list.
*
* This function can't fail. */
listIter *listGetIterator(list *list, int direction)
{
listIter *iter;
if ((iter = zmalloc(sizeof(*iter))) == NULL) return NULL;
if (direction == AL_START_HEAD)
iter->next = list->head;
else
iter->next = list->tail;
iter->direction = direction;
return iter;
}
/* Release the iterator memory */
void listReleaseIterator(listIter *iter) {
zfree(iter);
}
/* Return the next element of an iterator.
* It's valid to remove the currently returned element using
* listDelNode(), but not to remove other elements.
*
* The function returns a pointer to the next element of the list,
* or NULL if there are no more elements, so the classical usage patter
* is:
*
* iter = listGetItarotr(list,<direction>);
* while ((node = listNextIterator(iter)) != NULL) {
* DoSomethingWith(listNodeValue(node));
* }
*
* */
listNode *listNextElement(listIter *iter)
{
listNode *current = iter->next;
if (current != NULL) {
if (iter->direction == AL_START_HEAD)
iter->next = current->next;
else
iter->next = current->prev;
}
return current;
}
/* Duplicate the whole list. On out of memory NULL is returned.
* On success a copy of the original list is returned.
*
* The 'Dup' method set with listSetDupMethod() function is used
* to copy the node value. Otherwise the same pointer value of
* the original node is used as value of the copied node.
*
* The original list both on success or error is never modified. */
list *listDup(list *orig)
{
list *copy;
listIter *iter;
listNode *node;
if ((copy = listCreate()) == NULL)
return NULL;
copy->dup = orig->dup;
copy->free = orig->free;
copy->match = orig->match;
iter = listGetIterator(orig, AL_START_HEAD);
while((node = listNextElement(iter)) != NULL) {
void *value;
if (copy->dup) {
value = copy->dup(node->value);
if (value == NULL) {
listRelease(copy);
listReleaseIterator(iter);
return NULL;
}
} else
value = node->value;
if (listAddNodeTail(copy, value) == NULL) {
listRelease(copy);
listReleaseIterator(iter);
return NULL;
}
}
listReleaseIterator(iter);
return copy;
}
/* Search the list for a node matching a given key.
* The match is performed using the 'match' method
* set with listSetMatchMethod(). If no 'match' method
* is set, the 'value' pointer of every node is directly
* compared with the 'key' pointer.
*
* On success the first matching node pointer is returned
* (search starts from head). If no matching node exists
* NULL is returned. */
listNode *listSearchKey(list *list, void *key)
{
listIter *iter;
listNode *node;
iter = listGetIterator(list, AL_START_HEAD);
while((node = listNextElement(iter)) != NULL) {
if (list->match) {
if (list->match(node->value, key)) {
listReleaseIterator(iter);
return node;
}
} else {
if (key == node->value) {
listReleaseIterator(iter);
return node;
}
}
}
listReleaseIterator(iter);
return NULL;
}
/* Return the element at the specified zero-based index
* where 0 is the head, 1 is the element next to head
* and so on. Negative integers are used in order to count
* from the tail, -1 is the last element, -2 the penultimante
* and so on. If the index is out of range NULL is returned. */
listNode *listIndex(list *list, int index) {
listNode *n;
if (index < 0) {
index = (-index)-1;
n = list->tail;
while(index-- && n) n = n->prev;
} else {
n = list->head;
while(index-- && n) n = n->next;
}
return n;
}
/* adlist.h - A generic doubly linked list implementation
*
* Copyright (c) 2006-2009, Salvatore Sanfilippo <antirez at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef __ADLIST_H__
#define __ADLIST_H__
/* Node, List, and Iterator are the only data structures used currently. */
typedef struct listNode {
struct listNode *prev;
struct listNode *next;
void *value;
} listNode;
typedef struct list {
listNode *head;
listNode *tail;
void *(*dup)(void *ptr);
void (*free)(void *ptr);
int (*match)(void *ptr, void *key);
unsigned int len;
} list;
typedef struct listIter {
listNode *next;
listNode *prev;
int direction;
} listIter;
/* Functions implemented as macros */
#define listLength(l) ((l)->len)
#define listFirst(l) ((l)->head)
#define listLast(l) ((l)->tail)
#define listPrevNode(n) ((n)->prev)
#define listNextNode(n) ((n)->next)
#define listNodeValue(n) ((n)->value)
#define listSetDupMethod(l,m) ((l)->dup = (m))
#define listSetFreeMethod(l,m) ((l)->free = (m))
#define listSetMatchMethod(l,m) ((l)->match = (m))
#define listGetDupMethod(l) ((l)->dup)
#define listGetFree(l) ((l)->free)
#define listGetMatchMethod(l) ((l)->match)
/* Prototypes */
list *listCreate(void);
void listRelease(list *list);
list *listAddNodeHead(list *list, void *value);
list *listAddNodeTail(list *list, void *value);
void listDelNode(list *list, listNode *node);
listIter *listGetIterator(list *list, int direction);
listNode *listNextElement(listIter *iter);
void listReleaseIterator(listIter *iter);
list *listDup(list *orig);
listNode *listSearchKey(list *list, void *key);
listNode *listIndex(list *list, int index);
/* Directions for iterators */
#define AL_START_HEAD 0
#define AL_START_TAIL 1
#endif /* __ADLIST_H__ */
/* A simple event-driven programming library. Originally I wrote this code
* for the Jim's event-loop (Jim is a Tcl interpreter) but later translated
* it in form of a library for easy reuse.
*
* Copyright (c) 2006-2009, Salvatore Sanfilippo <antirez at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include "ae.h"
#include "zmalloc.h"
aeEventLoop *aeCreateEventLoop(void) {
aeEventLoop *eventLoop;
eventLoop = zmalloc(sizeof(*eventLoop));
if (!eventLoop) return NULL;
eventLoop->fileEventHead = NULL;
eventLoop->timeEventHead = NULL;
eventLoop->timeEventNextId = 0;
eventLoop->stop = 0;
return eventLoop;
}
void aeDeleteEventLoop(aeEventLoop *eventLoop) {
zfree(eventLoop);
}
void aeStop(aeEventLoop *eventLoop) {
eventLoop->stop = 1;
}
int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask,
aeFileProc *proc, void *clientData,
aeEventFinalizerProc *finalizerProc)
{
aeFileEvent *fe;
fe = zmalloc(sizeof(*fe));
if (fe == NULL) return AE_ERR;
fe->fd = fd;
fe->mask = mask;
fe->fileProc = proc;
fe->finalizerProc = finalizerProc;
fe->clientData = clientData;
fe->next = eventLoop->fileEventHead;
eventLoop->fileEventHead = fe;
return AE_OK;
}
void aeDeleteFileEvent(aeEventLoop *eventLoop, int fd, int mask)
{
aeFileEvent *fe, *prev = NULL;
fe = eventLoop->fileEventHead;
while(fe) {
if (fe->fd == fd && fe->mask == mask) {
if (prev == NULL)
eventLoop->fileEventHead = fe->next;
else
prev->next = fe->next;
if (fe->finalizerProc)
fe->finalizerProc(eventLoop, fe->clientData);
zfree(fe);
return;
}
prev = fe;
fe = fe->next;
}
}
static void aeGetTime(long *seconds, long *milliseconds)
{
struct timeval tv;
gettimeofday(&tv, NULL);
*seconds = tv.tv_sec;
*milliseconds = tv.tv_usec/1000;
}
static void aeAddMillisecondsToNow(long long milliseconds, long *sec, long *ms) {
long cur_sec, cur_ms, when_sec, when_ms;
aeGetTime(&cur_sec, &cur_ms);
when_sec = cur_sec + milliseconds/1000;
when_ms = cur_ms + milliseconds%1000;
if (when_ms >= 1000) {
when_sec ++;
when_ms -= 1000;
}
*sec = when_sec;
*ms = when_ms;
}
long long aeCreateTimeEvent(aeEventLoop *eventLoop, long long milliseconds,
aeTimeProc *proc, void *clientData,
aeEventFinalizerProc *finalizerProc)
{
long long id = eventLoop->timeEventNextId++;
aeTimeEvent *te;
te = zmalloc(sizeof(*te));
if (te == NULL) return AE_ERR;
te->id = id;
aeAddMillisecondsToNow(milliseconds,&te->when_sec,&te->when_ms);
te->timeProc = proc;
te->finalizerProc = finalizerProc;
te->clientData = clientData;
te->next = eventLoop->timeEventHead;
eventLoop->timeEventHead = te;
return id;
}
int aeDeleteTimeEvent(aeEventLoop *eventLoop, long long id)
{
aeTimeEvent *te, *prev = NULL;
te = eventLoop->timeEventHead;
while(te) {
if (te->id == id) {
if (prev == NULL)
eventLoop->timeEventHead = te->next;
else
prev->next = te->next;
if (te->finalizerProc)
te->finalizerProc(eventLoop, te->clientData);
zfree(te);
return AE_OK;
}
prev = te;
te = te->next;
}
return AE_ERR; /* NO event with the specified ID found */
}
/* Search the first timer to fire.
* This operation is useful to know how many time the select can be
* put in sleep without to delay any event.
* If there are no timers NULL is returned.
*
* Note that's O(N) since time events are unsorted. */
static aeTimeEvent *aeSearchNearestTimer(aeEventLoop *eventLoop)
{
aeTimeEvent *te = eventLoop->timeEventHead;
aeTimeEvent *nearest = NULL;
while(te) {
if (!nearest || te->when_sec < nearest->when_sec ||
(te->when_sec == nearest->when_sec &&
te->when_ms < nearest->when_ms))
nearest = te;
te = te->next;
}
return nearest;
}
/* Process every pending time event, then every pending file event
* (that may be registered by time event callbacks just processed).
* Without special flags the function sleeps until some file event
* fires, or when the next time event occurrs (if any).
*
* If flags is 0, the function does nothing and returns.
* if flags has AE_ALL_EVENTS set, all the kind of events are processed.
* if flags has AE_FILE_EVENTS set, file events are processed.
* if flags has AE_TIME_EVENTS set, time events are processed.
* if flags has AE_DONT_WAIT set the function returns ASAP until all
* the events that's possible to process without to wait are processed.
*
* The function returns the number of events processed. */
int aeProcessEvents(aeEventLoop *eventLoop, int flags)
{
int maxfd = 0, numfd = 0, processed = 0;
fd_set rfds, wfds, efds;
aeFileEvent *fe = eventLoop->fileEventHead;
aeTimeEvent *te;
long long maxId;
AE_NOTUSED(flags);
/* Nothing to do? return ASAP */
if (!(flags & AE_TIME_EVENTS) && !(flags & AE_FILE_EVENTS)) return 0;
FD_ZERO(&rfds);
FD_ZERO(&wfds);
FD_ZERO(&efds);
/* Check file events */
if (flags & AE_FILE_EVENTS) {
while (fe != NULL) {
if (fe->mask & AE_READABLE) FD_SET(fe->fd, &rfds);
if (fe->mask & AE_WRITABLE) FD_SET(fe->fd, &wfds);
if (fe->mask & AE_EXCEPTION) FD_SET(fe->fd, &efds);
if (maxfd < fe->fd) maxfd = fe->fd;
numfd++;
fe = fe->next;
}
}
/* Note that we want call select() even if there are no
* file events to process as long as we want to process time
* events, in order to sleep until the next time event is ready
* to fire. */
if (numfd || ((flags & AE_TIME_EVENTS) && !(flags & AE_DONT_WAIT))) {
int retval;
aeTimeEvent *shortest = NULL;
struct timeval tv, *tvp;
if (flags & AE_TIME_EVENTS && !(flags & AE_DONT_WAIT))
shortest = aeSearchNearestTimer(eventLoop);
if (shortest) {
long now_sec, now_ms;
/* Calculate the time missing for the nearest
* timer to fire. */
aeGetTime(&now_sec, &now_ms);
tvp = &tv;
tvp->tv_sec = shortest->when_sec - now_sec;
if (shortest->when_ms < now_ms) {
tvp->tv_usec = ((shortest->when_ms+1000) - now_ms)*1000;
tvp->tv_sec --;
} else {
tvp->tv_usec = (shortest->when_ms - now_ms)*1000;
}
} else {
/* If we have to check for events but need to return
* ASAP because of AE_DONT_WAIT we need to se the timeout
* to zero */
if (flags & AE_DONT_WAIT) {
tv.tv_sec = tv.tv_usec = 0;
tvp = &tv;
} else {
/* Otherwise we can block */
tvp = NULL; /* wait forever */
}
}
retval = select(maxfd+1, &rfds, &wfds, &efds, tvp);
if (retval > 0) {
fe = eventLoop->fileEventHead;
while(fe != NULL) {
int fd = (int) fe->fd;
if ((fe->mask & AE_READABLE && FD_ISSET(fd, &rfds)) ||
(fe->mask & AE_WRITABLE && FD_ISSET(fd, &wfds)) ||
(fe->mask & AE_EXCEPTION && FD_ISSET(fd, &efds)))
{
int mask = 0;
if (fe->mask & AE_READABLE && FD_ISSET(fd, &rfds))
mask |= AE_READABLE;
if (fe->mask & AE_WRITABLE && FD_ISSET(fd, &wfds))
mask |= AE_WRITABLE;
if (fe->mask & AE_EXCEPTION && FD_ISSET(fd, &efds))
mask |= AE_EXCEPTION;
fe->fileProc(eventLoop, fe->fd, fe->clientData, mask);
processed++;
/* After an event is processed our file event list
* may no longer be the same, so what we do
* is to clear the bit for this file descriptor and
* restart again from the head. */
fe = eventLoop->fileEventHead;
FD_CLR(fd, &rfds);
FD_CLR(fd, &wfds);
FD_CLR(fd, &efds);
} else {
fe = fe->next;
}
}
}
}
/* Check time events */
if (flags & AE_TIME_EVENTS) {
te = eventLoop->timeEventHead;
maxId = eventLoop->timeEventNextId-1;
while(te) {
long now_sec, now_ms;
long long id;
if (te->id > maxId) {
te = te->next;
continue;
}
aeGetTime(&now_sec, &now_ms);
if (now_sec > te->when_sec ||
(now_sec == te->when_sec && now_ms >= te->when_ms))
{
int retval;
id = te->id;
retval = te->timeProc(eventLoop, id, te->clientData);
/* After an event is processed our time event list may
* no longer be the same, so we restart from head.
* Still we make sure to don't process events registered
* by event handlers itself in order to don't loop forever.
* To do so we saved the max ID we want to handle. */
if (retval != AE_NOMORE) {
aeAddMillisecondsToNow(retval,&te->when_sec,&te->when_ms);
} else {
aeDeleteTimeEvent(eventLoop, id);
}
te = eventLoop->timeEventHead;
} else {
te = te->next;
}
}
}
return processed; /* return the number of processed file/time events */
}
/* Wait for millseconds until the given file descriptor becomes
* writable/readable/exception */
int aeWait(int fd, int mask, long long milliseconds) {
struct timeval tv;
fd_set rfds, wfds, efds;
int retmask = 0, retval;
tv.tv_sec = milliseconds/1000;
tv.tv_usec = (milliseconds%1000)*1000;
FD_ZERO(&rfds);
FD_ZERO(&wfds);
FD_ZERO(&efds);
if (mask & AE_READABLE) FD_SET(fd,&rfds);
if (mask & AE_WRITABLE) FD_SET(fd,&wfds);
if (mask & AE_EXCEPTION) FD_SET(fd,&efds);
if ((retval = select(fd+1, &rfds, &wfds, &efds, &tv)) > 0) {
if (FD_ISSET(fd,&rfds)) retmask |= AE_READABLE;
if (FD_ISSET(fd,&wfds)) retmask |= AE_WRITABLE;
if (FD_ISSET(fd,&efds)) retmask |= AE_EXCEPTION;
return retmask;
} else {
return retval;
}
}
void aeMain(aeEventLoop *eventLoop)
{
eventLoop->stop = 0;
while (!eventLoop->stop)
aeProcessEvents(eventLoop, AE_ALL_EVENTS);
}
/* A simple event-driven programming library. Originally I wrote this code
* for the Jim's event-loop (Jim is a Tcl interpreter) but later translated
* it in form of a library for easy reuse.
*
* Copyright (c) 2006-2009, Salvatore Sanfilippo <antirez at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef __AE_H__
#define __AE_H__
struct aeEventLoop;
/* Types and data structures */
typedef void aeFileProc(struct aeEventLoop *eventLoop, int fd, void *clientData, int mask);
typedef int aeTimeProc(struct aeEventLoop *eventLoop, long long id, void *clientData);
typedef void aeEventFinalizerProc(struct aeEventLoop *eventLoop, void *clientData);
/* File event structure */
typedef struct aeFileEvent {
int fd;
int mask; /* one of AE_(READABLE|WRITABLE|EXCEPTION) */
aeFileProc *fileProc;
aeEventFinalizerProc *finalizerProc;
void *clientData;
struct aeFileEvent *next;
} aeFileEvent;
/* Time event structure */
typedef struct aeTimeEvent {
long long id; /* time event identifier. */
long when_sec; /* seconds */
long when_ms; /* milliseconds */
aeTimeProc *timeProc;
aeEventFinalizerProc *finalizerProc;
void *clientData;
struct aeTimeEvent *next;
} aeTimeEvent;
/* State of an event based program */
typedef struct aeEventLoop {
long long timeEventNextId;
aeFileEvent *fileEventHead;
aeTimeEvent *timeEventHead;
int stop;
} aeEventLoop;
/* Defines */
#define AE_OK 0
#define AE_ERR -1
#define AE_READABLE 1
#define AE_WRITABLE 2
#define AE_EXCEPTION 4
#define AE_FILE_EVENTS 1
#define AE_TIME_EVENTS 2
#define AE_ALL_EVENTS (AE_FILE_EVENTS|AE_TIME_EVENTS)
#define AE_DONT_WAIT 4
#define AE_NOMORE -1
/* Macros */
#define AE_NOTUSED(V) ((void) V)
/* Prototypes */
aeEventLoop *aeCreateEventLoop(void);
void aeDeleteEventLoop(aeEventLoop *eventLoop);
void aeStop(aeEventLoop *eventLoop);
int aeCreateFileEvent(aeEventLoop *eventLoop, int fd, int mask,
aeFileProc *proc, void *clientData,
aeEventFinalizerProc *finalizerProc);
void aeDeleteFileEvent(aeEventLoop *eventLoop, int fd, int mask);
long long aeCreateTimeEvent(aeEventLoop *eventLoop, long long milliseconds,
aeTimeProc *proc, void *clientData,
aeEventFinalizerProc *finalizerProc);
int aeDeleteTimeEvent(aeEventLoop *eventLoop, long long id);
int aeProcessEvents(aeEventLoop *eventLoop, int flags);
int aeWait(int fd, int mask, long long milliseconds);
void aeMain(aeEventLoop *eventLoop);
#endif
/* anet.c -- Basic TCP socket stuff made a bit less boring
*
* Copyright (c) 2006-2009, Salvatore Sanfilippo <antirez at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <netdb.h>
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include "anet.h"
static void anetSetError(char *err, const char *fmt, ...)
{
va_list ap;
if (!err) return;
va_start(ap, fmt);
vsnprintf(err, ANET_ERR_LEN, fmt, ap);
va_end(ap);
}
int anetNonBlock(char *err, int fd)
{
int flags;
/* Set the socket nonblocking.
* Note that fcntl(2) for F_GETFL and F_SETFL can't be
* interrupted by a signal. */
if ((flags = fcntl(fd, F_GETFL)) == -1) {
anetSetError(err, "fcntl(F_GETFL): %s\n", strerror(errno));
return ANET_ERR;
}
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
anetSetError(err, "fcntl(F_SETFL,O_NONBLOCK): %s\n", strerror(errno));
return ANET_ERR;
}
return ANET_OK;
}
int anetTcpNoDelay(char *err, int fd)
{
int yes = 1;
if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes)) == -1)
{
anetSetError(err, "setsockopt TCP_NODELAY: %s\n", strerror(errno));
return ANET_ERR;
}
return ANET_OK;
}
int anetSetSendBuffer(char *err, int fd, int buffsize)
{
if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, &buffsize, sizeof(buffsize)) == -1)
{
anetSetError(err, "setsockopt SO_SNDBUF: %s\n", strerror(errno));
return ANET_ERR;
}
return ANET_OK;
}
int anetTcpKeepAlive(char *err, int fd)
{
int yes = 1;
if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof(yes)) == -1) {
anetSetError(err, "setsockopt SO_KEEPALIVE: %s\n", strerror(errno));
return ANET_ERR;
}
return ANET_OK;
}
int anetResolve(char *err, char *host, char *ipbuf)
{
struct sockaddr_in sa;
sa.sin_family = AF_INET;
if (inet_aton(host, &sa.sin_addr) == 0) {
struct hostent *he;
he = gethostbyname(host);
if (he == NULL) {
anetSetError(err, "can't resolve: %s\n", host);
return ANET_ERR;
}
memcpy(&sa.sin_addr, he->h_addr, sizeof(struct in_addr));
}
strcpy(ipbuf,inet_ntoa(sa.sin_addr));
return ANET_OK;
}
#define ANET_CONNECT_NONE 0
#define ANET_CONNECT_NONBLOCK 1
static int anetTcpGenericConnect(char *err, char *addr, int port, int flags)
{
int s, on = 1;
struct sockaddr_in sa;
if ((s = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
anetSetError(err, "creating socket: %s\n", strerror(errno));
return ANET_ERR;
}
/* Make sure connection-intensive things like the redis benckmark
* will be able to close/open sockets a zillion of times */
setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
sa.sin_family = AF_INET;
sa.sin_port = htons(port);
if (inet_aton(addr, &sa.sin_addr) == 0) {
struct hostent *he;
he = gethostbyname(addr);
if (he == NULL) {
anetSetError(err, "can't resolve: %s\n", addr);
close(s);
return ANET_ERR;
}
memcpy(&sa.sin_addr, he->h_addr, sizeof(struct in_addr));
}
if (flags & ANET_CONNECT_NONBLOCK) {
if (anetNonBlock(err,s) != ANET_OK)
return ANET_ERR;
}
if (connect(s, (struct sockaddr*)&sa, sizeof(sa)) == -1) {
if (errno == EINPROGRESS &&
flags & ANET_CONNECT_NONBLOCK)
return s;
anetSetError(err, "connect: %s\n", strerror(errno));
close(s);
return ANET_ERR;
}
return s;
}
int anetTcpConnect(char *err, char *addr, int port)
{
return anetTcpGenericConnect(err,addr,port,ANET_CONNECT_NONE);
}
int anetTcpNonBlockConnect(char *err, char *addr, int port)
{
return anetTcpGenericConnect(err,addr,port,ANET_CONNECT_NONBLOCK);
}
/* Like read(2) but make sure 'count' is read before to return
* (unless error or EOF condition is encountered) */
int anetRead(int fd, void *buf, int count)
{
int nread, totlen = 0;
while(totlen != count) {
nread = read(fd,buf,count-totlen);
if (nread == 0) return totlen;
if (nread == -1) return -1;
totlen += nread;
buf += nread;
}
return totlen;
}
/* Like write(2) but make sure 'count' is read before to return
* (unless error is encountered) */
int anetWrite(int fd, void *buf, int count)
{
int nwritten, totlen = 0;
while(totlen != count) {
nwritten = write(fd,buf,count-totlen);
if (nwritten == 0) return totlen;
if (nwritten == -1) return -1;
totlen += nwritten;
buf += nwritten;
}
return totlen;
}
int anetTcpServer(char *err, int port, char *bindaddr)
{
int s, on = 1;
struct sockaddr_in sa;
if ((s = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
anetSetError(err, "socket: %s\n", strerror(errno));
return ANET_ERR;
}
if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) {
anetSetError(err, "setsockopt SO_REUSEADDR: %s\n", strerror(errno));
close(s);
return ANET_ERR;
}
memset(&sa,0,sizeof(sa));
sa.sin_family = AF_INET;
sa.sin_port = htons(port);
sa.sin_addr.s_addr = htonl(INADDR_ANY);
if (bindaddr) {
if (inet_aton(bindaddr, &sa.sin_addr) == 0) {
anetSetError(err, "Invalid bind address\n");
close(s);
return ANET_ERR;
}
}
if (bind(s, (struct sockaddr*)&sa, sizeof(sa)) == -1) {
anetSetError(err, "bind: %s\n", strerror(errno));
close(s);
return ANET_ERR;
}
if (listen(s, 32) == -1) {
anetSetError(err, "listen: %s\n", strerror(errno));
close(s);
return ANET_ERR;
}
return s;
}
int anetAccept(char *err, int serversock, char *ip, int *port)
{
int fd;
struct sockaddr_in sa;
unsigned int saLen;
while(1) {
saLen = sizeof(sa);
fd = accept(serversock, (struct sockaddr*)&sa, &saLen);
if (fd == -1) {
if (errno == EINTR)
continue;
else {
anetSetError(err, "accept: %s\n", strerror(errno));
return ANET_ERR;
}
}
break;
}
if (ip) strcpy(ip,inet_ntoa(sa.sin_addr));
if (port) *port = ntohs(sa.sin_port);
return fd;
}
/* anet.c -- Basic TCP socket stuff made a bit less boring
*
* Copyright (c) 2006-2009, Salvatore Sanfilippo <antirez at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef ANET_H
#define ANET_H
#define ANET_OK 0
#define ANET_ERR -1
#define ANET_ERR_LEN 256
int anetTcpConnect(char *err, char *addr, int port);
int anetTcpNonBlockConnect(char *err, char *addr, int port);
int anetRead(int fd, void *buf, int count);
int anetResolve(char *err, char *host, char *ipbuf);
int anetTcpServer(char *err, int port, char *bindaddr);
int anetAccept(char *err, int serversock, char *ip, int *port);
int anetWrite(int fd, void *buf, int count);
int anetNonBlock(char *err, int fd);
int anetTcpNoDelay(char *err, int fd);
int anetTcpKeepAlive(char *err, int fd);
#endif
/* Redis benchmark utility.
*
* Copyright (c) 2006-2009, Salvatore Sanfilippo <antirez at gmail dot com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of Redis nor the names of its contributors may be used
* to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/time.h>
#include <signal.h>
#include <assert.h>
#include "ae.h"
#include "anet.h"
#include "sds.h"
#include "adlist.h"
#include "zmalloc.h"
#define REPLY_INT 0
#define REPLY_RETCODE 1
#define REPLY_BULK 2
#define CLIENT_CONNECTING 0
#define CLIENT_SENDQUERY 1
#define CLIENT_READREPLY 2
#define MAX_LATENCY 5000
#define REDIS_NOTUSED(V) ((void) V)
static struct config {
int numclients;
int requests;
int liveclients;
int donerequests;
int keysize;
int datasize;
aeEventLoop *el;
char *hostip;
int hostport;
int keepalive;
long long start;
long long totlatency;
int *latency;
list *clients;
int quiet;
int loop;
} config;
typedef struct _client {
int state;
int fd;
sds obuf;
sds ibuf;
int readlen; /* readlen == -1 means read a single line */
unsigned int written; /* bytes of 'obuf' already written */
int replytype;
long long start; /* start time in milliseconds */
} *client;
/* Prototypes */
static void writeHandler(aeEventLoop *el, int fd, void *privdata, int mask);
static void createMissingClients(client c);
/* Implementation */
static long long mstime(void) {
struct timeval tv;
long long mst;
gettimeofday(&tv, NULL);
mst = ((long)tv.tv_sec)*1000;
mst += tv.tv_usec/1000;
return mst;
}
static void freeClient(client c) {
listNode *ln;
aeDeleteFileEvent(config.el,c->fd,AE_WRITABLE);
aeDeleteFileEvent(config.el,c->fd,AE_READABLE);
sdsfree(c->ibuf);
sdsfree(c->obuf);
close(c->fd);
zfree(c);
config.liveclients--;
ln = listSearchKey(config.clients,c);
assert(ln != NULL);
listDelNode(config.clients,ln);
}
static void freeAllClients(void) {
listNode *ln = config.clients->head, *next;
while(ln) {
next = ln->next;
freeClient(ln->value);
ln = next;
}
}
static void resetClient(client c) {
aeDeleteFileEvent(config.el,c->fd,AE_WRITABLE);
aeDeleteFileEvent(config.el,c->fd,AE_READABLE);
aeCreateFileEvent(config.el,c->fd, AE_WRITABLE,writeHandler,c,NULL);
sdsfree(c->ibuf);
c->ibuf = sdsempty();
c->readlen = (c->replytype == REPLY_BULK) ? -1 : 0;
c->written = 0;
c->state = CLIENT_SENDQUERY;
c->start = mstime();
}
static void clientDone(client c) {
long long latency;
config.donerequests ++;
latency = mstime() - c->start;
if (latency > MAX_LATENCY) latency = MAX_LATENCY;
config.latency[latency]++;
if (config.donerequests == config.requests) {
freeClient(c);
aeStop(config.el);
return;
}
if (config.keepalive) {
resetClient(c);
} else {
config.liveclients--;
createMissingClients(c);
config.liveclients++;
freeClient(c);
}
}
static void readHandler(aeEventLoop *el, int fd, void *privdata, int mask)
{
char buf[1024];
int nread;
client c = privdata;
REDIS_NOTUSED(el);
REDIS_NOTUSED(fd);
REDIS_NOTUSED(mask);
nread = read(c->fd, buf, 1024);
if (nread == -1) {
fprintf(stderr, "Reading from socket: %s\n", strerror(errno));
freeClient(c);
return;
}
if (nread == 0) {
fprintf(stderr, "EOF from client\n");
freeClient(c);
return;
}
c->ibuf = sdscatlen(c->ibuf,buf,nread);
if (c->replytype == REPLY_INT ||
c->replytype == REPLY_RETCODE ||
(c->replytype == REPLY_BULK && c->readlen == -1)) {
char *p;
if ((p = strchr(c->ibuf,'\n')) != NULL) {
if (c->replytype == REPLY_BULK) {
*p = '\0';
*(p-1) = '\0';
if (memcmp(c->ibuf,"nil",3) == 0) {
clientDone(c);
return;
}
c->readlen = atoi(c->ibuf)+2;
c->ibuf = sdsrange(c->ibuf,(p-c->ibuf)+1,-1);
} else {
c->ibuf = sdstrim(c->ibuf,"\r\n");
clientDone(c);
return;
}
}
}
/* bulk read */
if ((unsigned)c->readlen == sdslen(c->ibuf))
clientDone(c);
}
static void writeHandler(aeEventLoop *el, int fd, void *privdata, int mask)
{
client c = privdata;
REDIS_NOTUSED(el);
REDIS_NOTUSED(fd);
REDIS_NOTUSED(mask);
if (c->state == CLIENT_CONNECTING) {
c->state = CLIENT_SENDQUERY;
c->start = mstime();
}
if (sdslen(c->obuf) > c->written) {
void *ptr = c->obuf+c->written;
int len = sdslen(c->obuf) - c->written;
int nwritten = write(c->fd, ptr, len);
if (nwritten == -1) {
fprintf(stderr, "Writing to socket: %s\n", strerror(errno));
freeClient(c);
return;
}
c->written += nwritten;
if (sdslen(c->obuf) == c->written) {
aeDeleteFileEvent(config.el,c->fd,AE_WRITABLE);
aeCreateFileEvent(config.el,c->fd,AE_READABLE,readHandler,c,NULL);
c->state = CLIENT_READREPLY;
}
}
}
static client createClient(void) {
client c = zmalloc(sizeof(struct _client));
char err[ANET_ERR_LEN];
c->fd = anetTcpNonBlockConnect(err,config.hostip,config.hostport);
if (c->fd == ANET_ERR) {
zfree(c);
fprintf(stderr,"Connect: %s\n",err);
return NULL;
}
anetTcpNoDelay(NULL,c->fd);
c->obuf = sdsempty();
c->ibuf = sdsempty();
c->readlen = 0;
c->written = 0;
c->state = CLIENT_CONNECTING;
aeCreateFileEvent(config.el, c->fd, AE_WRITABLE, writeHandler, c, NULL);
config.liveclients++;
listAddNodeTail(config.clients,c);
return c;
}
static void createMissingClients(client c) {
while(config.liveclients < config.numclients) {
client new = createClient();
if (!new) continue;
sdsfree(new->obuf);
new->obuf = sdsdup(c->obuf);
new->replytype = c->replytype;
if (c->replytype == REPLY_BULK)
new->readlen = -1;
}
}
static void showLatencyReport(char *title) {
int j, seen = 0;
float perc, reqpersec;
reqpersec = (float)config.donerequests/((float)config.totlatency/1000);
if (!config.quiet) {
printf("====== %s ======\n", title);
printf(" %d requests completed in %.2f seconds\n", config.donerequests,
(float)config.totlatency/1000);
printf(" %d parallel clients\n", config.numclients);
printf(" %d bytes payload\n", config.datasize);
printf(" keep alive: %d\n", config.keepalive);
printf("\n");
for (j = 0; j <= MAX_LATENCY; j++) {
if (config.latency[j]) {
seen += config.latency[j];
perc = ((float)seen*100)/config.donerequests;
printf("%.2f%% <= %d milliseconds\n", perc, j);
}
}
printf("%.2f requests per second\n\n", reqpersec);
} else {
printf("%s: %.2f requests per second\n", title, reqpersec);
}
}
static void prepareForBenchmark(void)
{
memset(config.latency,0,sizeof(int)*(MAX_LATENCY+1));
config.start = mstime();
config.donerequests = 0;
}
static void endBenchmark(char *title) {
config.totlatency = mstime()-config.start;
showLatencyReport(title);
freeAllClients();
}
void parseOptions(int argc, char **argv) {
int i;
for (i = 1; i < argc; i++) {
int lastarg = i==argc-1;
if (!strcmp(argv[i],"-c") && !lastarg) {
config.numclients = atoi(argv[i+1]);
i++;
} else if (!strcmp(argv[i],"-n") && !lastarg) {
config.requests = atoi(argv[i+1]);
i++;
} else if (!strcmp(argv[i],"-k") && !lastarg) {
config.keepalive = atoi(argv[i+1]);
i++;
} else if (!strcmp(argv[i],"-h") && !lastarg) {
char *ip = zmalloc(32);
if (anetResolve(NULL,argv[i+1],ip) == ANET_ERR) {
printf("Can't resolve %s\n", argv[i]);
exit(1);
}
config.hostip = ip;
i++;
} else if (!strcmp(argv[i],"-p") && !lastarg) {
config.hostport = atoi(argv[i+1]);
i++;
} else if (!strcmp(argv[i],"-d") && !lastarg) {
config.datasize = atoi(argv[i+1]);
i++;
if (config.datasize < 1) config.datasize=1;
if (config.datasize > 1024*1024) config.datasize = 1024*1024;
} else if (!strcmp(argv[i],"-q")) {
config.quiet = 1;
} else if (!strcmp(argv[i],"-l")) {
config.loop = 1;
} else {
printf("Wrong option '%s' or option argument missing\n\n",argv[i]);
printf("Usage: redis-benchmark [-h <host>] [-p <port>] [-c <clients>] [-n <requests]> [-k <boolean>]\n\n");
printf(" -h <hostname> Server hostname (default 127.0.0.1)\n");
printf(" -p <hostname> Server port (default 6379)\n");
printf(" -c <clients> Number of parallel connections (default 50)\n");
printf(" -n <requests> Total number of requests (default 10000)\n");
printf(" -d <size> Data size of SET/GET value in bytes (default 2)\n");
printf(" -k <boolean> 1=keep alive 0=reconnect (default 1)\n");
printf(" -q Quiet. Just show query/sec values\n");
printf(" -l Loop. Run the tests forever\n");
exit(1);
}
}
}
int main(int argc, char **argv) {
client c;
signal(SIGHUP, SIG_IGN);
signal(SIGPIPE, SIG_IGN);
config.numclients = 50;
config.requests = 10000;
config.liveclients = 0;
config.el = aeCreateEventLoop();
config.keepalive = 1;
config.donerequests = 0;
config.datasize = 3;
config.quiet = 0;
config.loop = 0;
config.latency = NULL;
config.clients = listCreate();
config.latency = zmalloc(sizeof(int)*(MAX_LATENCY+1));
config.hostip = "127.0.0.1";
config.hostport = 6379;
parseOptions(argc,argv);
if (config.keepalive == 0) {
printf("WARNING: keepalive disabled, you probably need 'echo 1 > /proc/sys/net/ipv4/tcp_tw_reuse' in order to use a lot of clients/requests\n");
}
do {
prepareForBenchmark();
c = createClient();
if (!c) exit(1);
c->obuf = sdscat(c->obuf,"PING\r\n");
c->replytype = REPLY_RETCODE;
createMissingClients(c);
aeMain(config.el);
endBenchmark("PING");
prepareForBenchmark();
c = createClient();
if (!c) exit(1);
c->obuf = sdscatprintf(c->obuf,"SET foo %d\r\n",config.datasize);
{
char *data = zmalloc(config.datasize+2);
memset(data,'x',config.datasize);
data[config.datasize] = '\r';
data[config.datasize+1] = '\n';
c->obuf = sdscatlen(c->obuf,data,config.datasize+2);
}
c->replytype = REPLY_RETCODE;
createMissingClients(c);
aeMain(config.el);
endBenchmark("SET");
prepareForBenchmark();
c = createClient();
if (!c) exit(1);
c->obuf = sdscat(c->obuf,"GET foo\r\n");
c->replytype = REPLY_BULK;
c->readlen = -1;
createMissingClients(c);
aeMain(config.el);
endBenchmark("GET");
prepareForBenchmark();
c = createClient();
if (!c) exit(1);
c->obuf = sdscat(c->obuf,"INCR counter\r\n");
c->replytype = REPLY_INT;
createMissingClients(c);
aeMain(config.el);
endBenchmark("INCR");
prepareForBenchmark();
c = createClient();
if (!c) exit(1);
c->obuf = sdscat(c->obuf,"LPUSH mylist 3\r\nbar\r\n");
c->replytype = REPLY_INT;
createMissingClients(c);
aeMain(config.el);
endBenchmark("LPUSH");
prepareForBenchmark();
c = createClient();
if (!c) exit(1);
c->obuf = sdscat(c->obuf,"LPOP mylist\r\n");
c->replytype = REPLY_BULK;
c->readlen = -1;
createMissingClients(c);
aeMain(config.el);
endBenchmark("LPOP");
printf("\n");
} while(config.loop);
return 0;
}
Redis client libraries
----------------------
In this directory you'll find client libraries for different languages.
This are the latest releases available at the time this Redis tar.gz for this
release was created, and are good for most uses, but if you need more fresh
code or recent bugfixes read more.
How to get the lastest versions of client libraries source code
---------------------------------------------------------------
Note that while the PHP and Python versions are the most uptodate available
libraries, the Ruby and Erlang libraries have their own sites so you may want
to grab this libraries from their main sites:
Ruby lib source code:
http://github.com/ezmobius/redis-rb/tree/master
Erlang lib source code:
http://bitbucket.org/adroll/erldis/
For the languages with development code in the Redis SVN, check this urls for unstable versions of the libs:
Python lib source code:
http://code.google.com/p/redis/source/browse/#svn/trunk/client-libraries/python
PHP lib source code:
http://code.google.com/p/redis/source/browse/#svn/trunk/client-libraries/php
repo: 9e1f35ed7fdc7b3da7f5ff66a71d1975b85e2ae5
node: 7f98e864d76b0b2a7427049b943fb1c0dad0df2a
syntax: glob
*.beam
\ No newline at end of file
Copyright (c) 2009
adroll.com
Valentino Volonghi
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
LIBDIR=`erl -eval 'io:format("~s~n", [code:lib_dir()])' -s init stop -noshell`
all:
mkdir -p ebin/
(cd src;$(MAKE))
(cd test;$(MAKE))
clean: clean_tests
(cd src;$(MAKE) clean)
rm -rf erl_crash.dump *.beam
clean_tests:
(cd test;$(MAKE) clean)
rm -rf erl_crash.dump *.beam
test: clean
mkdir -p ebin/
(cd src;$(MAKE))
(cd test;$(MAKE))
(cd test;$(MAKE) test)
testrun: all
mkdir -p ebin/
(cd test;$(MAKE) test)
install: all
mkdir -p ${LIBDIR}/erldis-0.0.1/{ebin,include}
for i in ebin/*.beam; do install $$i $(LIBDIR)/erldis-0.0.1/$$i ; done
for i in include/*.hrl; do install $$i $(LIBDIR)/erldis-0.0.1/$$i ; done
-record(redis, {socket,buffer=[],reply_caller,parsers,remaining=0,pstate=empty,results=[]}).
\ No newline at end of file
include ../support/include.mk
all: $(EBIN_FILES)
debug:
$(MAKE) DEBUG=-DDEBUG
clean:
rm -rf $(EBIN_FILES) erl_crash.dump
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment