Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a69534967b | ||
|
|
5e38bc389c | ||
|
|
01f7db608d | ||
|
|
9faeff0215 | ||
|
|
fbd9dc6039 | ||
|
|
551ffc486c | ||
|
|
78dca8977f | ||
|
|
a6695e341b | ||
|
|
9fe4429a09 | ||
|
|
6fbcb19275 | ||
|
|
8375a6d0aa | ||
|
|
55024e9a9a | ||
|
|
3cc31da349 | ||
|
|
8c3507aa8f | ||
|
|
9f3abf436b | ||
|
|
ff47d5ebf6 | ||
|
|
b3fdddfd1f | ||
|
|
670084b724 | ||
|
|
03a5c0ecb5 | ||
|
|
683cf5b872 | ||
|
|
94f7979414 | ||
|
|
537f3053fa | ||
|
|
667e2991dc | ||
|
|
4284b07220 | ||
|
|
db6e874a25 | ||
|
|
dd36f18c76 | ||
|
|
d1f89bdc2b | ||
|
|
59ad68ca4f | ||
|
|
8fb8a474fd | ||
|
|
85b3db4065 | ||
|
|
3a706ba792 | ||
|
|
97fdcd8746 | ||
|
|
45fac9481a | ||
|
|
c3eac3ddff | ||
|
|
fdcadd16c6 | ||
|
|
ac48aada30 | ||
|
|
abe29c83e6 | ||
|
|
8fcce90b25 | ||
|
|
4ac8472cac | ||
|
|
f7b5e2d11e | ||
|
|
32a94beca4 | ||
|
|
f862e5ddfa | ||
|
|
32168a5e8a | ||
|
|
4bf7c5d0f8 | ||
|
|
33b37b1400 | ||
|
|
ae2cd2e16f | ||
|
|
8c0743d80e | ||
|
|
ef12b4c3eb | ||
|
|
1580a378b8 | ||
|
|
467848460a | ||
|
|
5e707c66dd | ||
|
|
65d47452f8 | ||
|
|
09757420a4 | ||
|
|
134345e332 | ||
|
|
f89a7bbe11 | ||
|
|
dbcf381099 | ||
|
|
6bc74b4393 | ||
|
|
1aff711d90 | ||
|
|
866f6bcc80 | ||
|
|
29ed617ac4 | ||
|
|
a43e45e391 | ||
|
|
25a4699a9e | ||
|
|
09a3976478 | ||
|
|
60c748628b | ||
|
|
96614f5866 | ||
|
|
d1df50ad59 | ||
|
|
282c6106ce | ||
|
|
25b5abe8cf | ||
|
|
a7c46df612 | ||
|
|
8dbd0134e5 | ||
|
|
0c8999dfeb | ||
|
|
0f9ab49d58 | ||
|
|
4be50f7fbb | ||
|
|
8fc2247c9b | ||
|
|
ebd25710dd | ||
|
|
524b2626f7 | ||
|
|
78bde472c0 | ||
|
|
07b2779bff | ||
|
|
4024220fe6 | ||
|
|
50669f7b66 | ||
|
|
9b53f79e00 | ||
|
|
9840df7bad | ||
|
|
b5dc2c5b91 | ||
|
|
cbd1607c6a | ||
|
|
9a039f76c2 | ||
|
|
28b54b5d94 | ||
|
|
ff58d5a380 | ||
|
|
eb7b3427e7 | ||
|
|
4d7b3815d5 | ||
|
|
9fa51dd4d1 | ||
|
|
d558af8e6f | ||
|
|
e426e77896 | ||
|
|
8a44e10848 | ||
|
|
0bfdd4bb49 | ||
|
|
098bb97703 | ||
|
|
316e1db970 | ||
|
|
c8a6cd94b3 | ||
|
|
7b11dfe05e | ||
|
|
156029b336 | ||
|
|
06e9b3cad8 | ||
|
|
ab5f20d19c | ||
|
|
70e39481ab | ||
|
|
294bcfc4d2 | ||
|
|
0c29cadd46 | ||
|
|
3df2ab673c | ||
|
|
c970507478 | ||
|
|
bb10ad9c13 | ||
|
|
c785d4cccc | ||
|
|
e1fd73f634 | ||
|
|
315536d012 | ||
|
|
f0e67afd94 | ||
|
|
1f3c097b1c | ||
|
|
e4db34e97c | ||
|
|
f9765990b2 | ||
|
|
54ca682887 | ||
|
|
95d880a7bf | ||
|
|
2e581217d9 | ||
|
|
121a9a3579 | ||
|
|
3dd113bf33 | ||
|
|
3e8f5113fa | ||
|
|
c145854bac | ||
|
|
65dbb3546a | ||
|
|
4c68319370 | ||
|
|
f3eab7a9b4 | ||
|
|
f3c4111edd | ||
|
|
07b4d33734 | ||
|
|
58fb90fdac | ||
|
|
c005315a64 | ||
|
|
e81b51ad8c | ||
|
|
8cc912db43 | ||
|
|
e905588062 | ||
|
|
b38996aab1 | ||
|
|
062564c261 | ||
|
|
d20d89e630 | ||
|
|
dc1c91a8bf | ||
|
|
350792bd72 | ||
|
|
ed95915d33 | ||
|
|
34afc91030 | ||
|
|
35a62f7408 | ||
|
|
855c97ce1b | ||
|
|
3cb03b68a4 | ||
|
|
e400ae60f5 | ||
|
|
a57eb3272d | ||
|
|
72c84acc99 | ||
|
|
58ecc0bd68 | ||
|
|
0baab66d16 | ||
|
|
670cf9598e | ||
|
|
41d7c90417 | ||
|
|
63feb93a9f | ||
|
|
0a6649a7c0 | ||
|
|
53377f8c6d | ||
|
|
f3efd529df | ||
|
|
fdc5dbd5b1 | ||
|
|
afe949efcb | ||
|
|
1066fed769 | ||
|
|
cd4f0dc775 | ||
|
|
58fea46976 | ||
|
|
ff3ca17bac | ||
|
|
04749ee015 | ||
|
|
60ff8095d6 | ||
|
|
e9cd75dcd1 | ||
|
|
ac6b656fd6 |
+109
@@ -14,6 +14,115 @@ HIGH: There is a critical bug that may affect a subset of users. Upgrade!
|
||||
CRITICAL: There is a critical bug affecting MOST USERS. Upgrade ASAP.
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
--[ Redis 2.8.16 ] Release date: 16 Sep 2014
|
||||
|
||||
# UPGRADE URGENCY: HIGH for Redis if you are using 2.8.15 + AOF.
|
||||
LOW for Sentinel.
|
||||
|
||||
* [FIX] The ability to load truncated AOF files introduced with Redis 2.8.15
|
||||
contains a bug fixed in this release: after loading the file was not
|
||||
truncated to the last valid command, so the new commands are appended
|
||||
after a non well formed command. This means that:
|
||||
|
||||
1) The first AOF rewrite triggered by the server will automatically
|
||||
fix the problem.
|
||||
2) However, if the server is restarted before the rewrite, Redis may
|
||||
not be able to load the file and you need to manually fix it.
|
||||
|
||||
In order to fix a corrupted file you should start the redis-check-aof
|
||||
utility WITHOUT the --fix option, just to check the offset where the
|
||||
corruption is found. Around the offset reported by the check utility
|
||||
you'll find, inside your AOF file, a command which is not complete
|
||||
according to the Redis protocol. Just remove this incomplete command
|
||||
leafing the file unaltered before and after the offending command,
|
||||
and restart the server.
|
||||
|
||||
IMPORTANT #1: Redis 2.8.15 is the only stable version of Redis with
|
||||
this bug so probably no actual real-world problem happened since the
|
||||
problem is automatically fixed at the first automatic AOF rewrite.
|
||||
|
||||
IMPORTANT #2: Before upgrading to Redis 2.8.16, if you are using Redis
|
||||
2.8.15 with AOF enabled, make sure to trigger a manual AOF rewrite
|
||||
using the BGREWRITEAOF command.
|
||||
|
||||
* [FIX] SAVE is no longer propagated to AOF / slaves.
|
||||
|
||||
--[ Redis 2.8.15 ] Release date: 12 Sep 2014
|
||||
|
||||
# UPGRADE URGENCY: LOW for Redis, HIGH for Sentinel.
|
||||
|
||||
* [FIX] Sentinel critical bug fixed: the absolute majority was computed in a
|
||||
wrong way because of a programming error. Now the implementation does
|
||||
what the specification says and the majority to authorize a failover
|
||||
(that should not be confused with the ODOWN quorum) is the majority of
|
||||
*all* the Sentinels ever seen for a given master, regardless of their
|
||||
current state.
|
||||
* [FIX] GETRANGE test no longer fails for 32 bit builds (Matt Stancliff).
|
||||
* [FIX] Limit SCAN latency when the hash table is in an odd state (very few
|
||||
populted buckets because rehashing is in progress). (Xiaost and
|
||||
Salvatore Sanfilippo)
|
||||
|
||||
* [NEW] Redis is now able to load truncated AOF files without requiring a
|
||||
redis-check-aof utility run. The default now is to load truncated
|
||||
(but apparently not corrupted) AOFs, you can change this in redis.conf.
|
||||
(Salvatore Sanfilippo).
|
||||
* [NEW] Sentinel: ability to announce itself with an arbitrary IP/port to work
|
||||
in the context of natted networks. However this is probably still
|
||||
not enough since there is no equivalent mechanism for slaves listed
|
||||
in the master INFO output. (Dara Kong and Salvatore Sanfilippo)
|
||||
|
||||
--[ Redis 2.8.14 ] Release date: 1 Sep 2014
|
||||
|
||||
# UPGRADE URGENCY: HIGH for Lua scripting users, the server could crash because
|
||||
of a bug introduced in Redis 2.8.10, otherwise LOW.
|
||||
LOW for Redis Sentinel.
|
||||
|
||||
* [FIX] Don't prevent use of shared integers if maxmemory policy is non-LRU.
|
||||
(Salvatore Sanfilippo)
|
||||
* [FIX] Fail SYNC if background save child aborted due to a signal.
|
||||
(Yossi Gottlieb)
|
||||
* [FIX] Different small redis-cli fixes. (Dov Murik, Charsyam, cubicdaiya,
|
||||
Kashif Rasul, Jan-Erik Rediger, Matt Stancliff)
|
||||
* [FIX] AIX compilation fixes. (Siah Lyimo)
|
||||
* [FIX] A number of other smaller issues.
|
||||
* [FIX] Improved SIGINT handling (Matt Stancliff, Salvatore Sanfilippo)
|
||||
* [FIX] Use unsigned types in SDS header to raise limit to 4GB.
|
||||
(Matt Stancliff, Salvatore Sanfilippo)
|
||||
* [FIX] Handle signed/unsigned comparisons with more care around the code.
|
||||
(Salvatore Sanfilippo)
|
||||
* [FIX] Colorized test output fixed to don't change the background color.
|
||||
(Mariano Pérez Rodríguez)
|
||||
* [FIX] More Sentinel IPv6 fixes. (Eiichi Sato)
|
||||
* [FIX] Deny CLIENT command in scripts. (Matt Stancliff)
|
||||
* [FIX] Allow datasets with more than 2 billion of keys, initial work.
|
||||
* [FIX] Fix a Lua scripting crash by storing the length of the static
|
||||
argv when first allocated. (Paddy Byers)
|
||||
|
||||
* [NEW] Pub/Sub PING. (Salvatore Sanfilippo)
|
||||
* [NEW] Much faster ZUNIONSTORE. (Kyle Hubert, Salvatore Sanfilippo)
|
||||
* [NEW] Faster ll2string() implementation. (Salvatore Sanfilippo)
|
||||
* [NEW] **WARNING, minor API change**: PUBSUB NUMSUB: return type modified
|
||||
to integer. (Matt Stancliff)
|
||||
* [NEW] redis-benchmark support for AUTH. (CharSyam)
|
||||
|
||||
--[ Redis 2.8.13 ] Release date: 14 Jul 2014
|
||||
|
||||
# UPGRADE URGENCY: LOW for Redis and Sentinel, this is a features enhancement
|
||||
release mostly. Since this release introduces the latency
|
||||
monitoring feature, Redis 2.8 users experiencing latency
|
||||
issues are strongly encouraged to upgrade.
|
||||
|
||||
* [FIX] CLIENT KILL minor backward compatibility fixes. (Salvatore Sanfilippo)
|
||||
* [FIX] Enable HAVE_ATOMIC for PowerPC. (Matt Stancliff)
|
||||
* [FIX] More robust PSYNC and AOF rewrites tests. (Salvatore Sanfilippo)
|
||||
* [FIX] Solaris build fixed. (Matt Stancliff, Salvatore Sanfilippo)
|
||||
|
||||
* [NEW] The new latency monitoring feature, as documented at
|
||||
http://redis.io/topics/latency-monitor (Salvatore Sanfilippo)
|
||||
* [NEW] The COMMAND command, exposing the Redis command table
|
||||
as an API. (Matt Stancliff)
|
||||
* [NEW] Update used memory with C11 __atomic. (Matt Stancliff)
|
||||
|
||||
--[ Redis 2.8.12 ] Release date: 23 Jun 2014
|
||||
|
||||
# UPGRADE URGENCY: HIGH for Redis, CRITICAL for Sentinel.
|
||||
|
||||
Vendored
+6
-1
@@ -60,10 +60,15 @@ endif
|
||||
|
||||
LUA_CFLAGS+= -O2 -Wall -DLUA_ANSI $(CFLAGS)
|
||||
LUA_LDFLAGS+= $(LDFLAGS)
|
||||
# lua's Makefile defines AR="ar rcu", which is unusual, and makes it more
|
||||
# challenging to cross-compile lua (and redis). These defines make it easier
|
||||
# to fit redis into cross-compilation environments, which typically set AR.
|
||||
AR=ar
|
||||
ARFLAGS=rcu
|
||||
|
||||
lua: .make-prerequisites
|
||||
@printf '%b %b\n' $(MAKECOLOR)MAKE$(ENDCOLOR) $(BINCOLOR)$@$(ENDCOLOR)
|
||||
cd lua/src && $(MAKE) all CFLAGS="$(LUA_CFLAGS)" MYLDFLAGS="$(LUA_LDFLAGS)"
|
||||
cd lua/src && $(MAKE) all CFLAGS="$(LUA_CFLAGS)" MYLDFLAGS="$(LUA_LDFLAGS)" AR="$(AR) $(ARFLAGS)"
|
||||
|
||||
.PHONY: lua
|
||||
|
||||
|
||||
Vendored
+4
@@ -5,6 +5,10 @@
|
||||
#define _BSD_SOURCE
|
||||
#endif
|
||||
|
||||
#if defined(_AIX)
|
||||
#define _ALL_SOURCE
|
||||
#endif
|
||||
|
||||
#if defined(__sun__)
|
||||
#define _POSIX_C_SOURCE 200112L
|
||||
#elif defined(__linux__) || defined(__OpenBSD__) || defined(__NetBSD__)
|
||||
|
||||
Vendored
+1
-1
@@ -35,7 +35,7 @@
|
||||
|
||||
#include "hiredis.h"
|
||||
|
||||
#if defined(__sun)
|
||||
#if defined(__sun) || defined(_AIX)
|
||||
#define AF_LOCAL AF_UNIX
|
||||
#endif
|
||||
|
||||
|
||||
Vendored
+7
-4
@@ -123,7 +123,7 @@ void sdsclear(sds s) {
|
||||
/* Enlarge the free space at the end of the sds string so that the caller
|
||||
* is sure that after calling this function can overwrite up to addlen
|
||||
* bytes after the end of the string, plus one more byte for nul term.
|
||||
*
|
||||
*
|
||||
* Note: this does not change the *length* of the sds string as returned
|
||||
* by sdslen(), but only the free buffer space we have. */
|
||||
sds sdsMakeRoomFor(sds s, size_t addlen) {
|
||||
@@ -200,10 +200,12 @@ size_t sdsAllocSize(sds s) {
|
||||
void sdsIncrLen(sds s, int incr) {
|
||||
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
|
||||
|
||||
assert(sh->free >= incr);
|
||||
if (incr >= 0)
|
||||
assert(sh->free >= (unsigned int)incr);
|
||||
else
|
||||
assert(sh->len >= (unsigned int)(-incr));
|
||||
sh->len += incr;
|
||||
sh->free -= incr;
|
||||
assert(sh->free >= 0);
|
||||
s[sh->len] = '\0';
|
||||
}
|
||||
|
||||
@@ -388,6 +390,7 @@ sds sdscatvprintf(sds s, const char *fmt, va_list ap) {
|
||||
buf[buflen-2] = '\0';
|
||||
va_copy(cpy,ap);
|
||||
vsnprintf(buf, buflen, fmt, cpy);
|
||||
va_end(ap);
|
||||
if (buf[buflen-2] != '\0') {
|
||||
if (buf != staticbuf) zfree(buf);
|
||||
buflen *= 2;
|
||||
@@ -457,7 +460,7 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
|
||||
i = initlen; /* Position of the next byte to write to dest str. */
|
||||
while(*f) {
|
||||
char next, *str;
|
||||
int l;
|
||||
unsigned int l;
|
||||
long long num;
|
||||
unsigned long long unum;
|
||||
|
||||
|
||||
Vendored
+2
-2
@@ -39,8 +39,8 @@
|
||||
typedef char *sds;
|
||||
|
||||
struct sdshdr {
|
||||
int len;
|
||||
int free;
|
||||
unsigned int len;
|
||||
unsigned int free;
|
||||
char buf[];
|
||||
};
|
||||
|
||||
|
||||
+59
-5
@@ -68,7 +68,7 @@ tcp-backlog 511
|
||||
# on a unix socket when not specified.
|
||||
#
|
||||
# unixsocket /tmp/redis.sock
|
||||
# unixsocketperm 755
|
||||
# unixsocketperm 700
|
||||
|
||||
# Close the connection after a client is idle for N seconds (0 to disable)
|
||||
timeout 0
|
||||
@@ -189,9 +189,18 @@ dir ./
|
||||
################################# REPLICATION #################################
|
||||
|
||||
# Master-Slave replication. Use slaveof to make a Redis instance a copy of
|
||||
# another Redis server. Note that the configuration is local to the slave
|
||||
# so for example it is possible to configure the slave to save the DB with a
|
||||
# different interval, or to listen to another port, and so on.
|
||||
# another Redis server. A few things to understand ASAP about Redis replication.
|
||||
#
|
||||
# 1) Redis replication is asynchronous, but you can configure a master to
|
||||
# stop accepting writes if it appears to be not connected with at least
|
||||
# a given number of slaves.
|
||||
# 2) Redis slaves are able to perform a partial resynchronization with the
|
||||
# master if the replication link is lost for a relatively small amount of
|
||||
# time. You may want to configure the replication backlog size (see the next
|
||||
# sections of this file) with a sensible value depending on your needs.
|
||||
# 3) Replication is automatic and does not need user intervention. After a
|
||||
# network partition slaves automatically try to reconnect to masters
|
||||
# and resynchronize with them.
|
||||
#
|
||||
# slaveof <masterip> <masterport>
|
||||
|
||||
@@ -520,6 +529,30 @@ no-appendfsync-on-rewrite no
|
||||
auto-aof-rewrite-percentage 100
|
||||
auto-aof-rewrite-min-size 64mb
|
||||
|
||||
# An AOF file may be found to be truncated at the end during the Redis
|
||||
# startup process, when the AOF data gets loaded back into memory.
|
||||
# This may happen when the system where Redis is running
|
||||
# crashes, especially when an ext4 filesystem is mounted without the
|
||||
# data=ordered option (however this can't happen when Redis itself
|
||||
# crashes or aborts but the operating system still works correctly).
|
||||
#
|
||||
# Redis can either exit with an error when this happens, or load as much
|
||||
# data as possible (the default now) and start if the AOF file is found
|
||||
# to be truncated at the end. The following option controls this behavior.
|
||||
#
|
||||
# If aof-load-truncated is set to yes, a truncated AOF file is loaded and
|
||||
# the Redis server starts emitting a log to inform the user of the event.
|
||||
# Otherwise if the option is set to no, the server aborts with an error
|
||||
# and refuses to start. When the option is set to no, the user requires
|
||||
# to fix the AOF file using the "redis-check-aof" utility before to restart
|
||||
# the server.
|
||||
#
|
||||
# Note that if the AOF file will be found to be corrupted in the middle
|
||||
# the server will still exit with an error. This option only applies when
|
||||
# Redis will try to read more data from the AOF file but not enough bytes
|
||||
# will be found.
|
||||
aof-load-truncated yes
|
||||
|
||||
################################ LUA SCRIPTING ###############################
|
||||
|
||||
# Max execution time of a Lua script in milliseconds.
|
||||
@@ -562,10 +595,31 @@ slowlog-log-slower-than 10000
|
||||
# You can reclaim memory used by the slow log with SLOWLOG RESET.
|
||||
slowlog-max-len 128
|
||||
|
||||
################################ LATENCY MONITOR ##############################
|
||||
|
||||
# The Redis latency monitoring subsystem samples different operations
|
||||
# at runtime in order to collect data related to possible sources of
|
||||
# latency of a Redis instance.
|
||||
#
|
||||
# Via the LATENCY command this information is available to the user that can
|
||||
# print graphs and obtain reports.
|
||||
#
|
||||
# The system only logs operations that were performed in a time equal or
|
||||
# greater than the amount of milliseconds specified via the
|
||||
# latency-monitor-threshold configuration directive. When its value is set
|
||||
# to zero, the latency monitor is turned off.
|
||||
#
|
||||
# By default latency monitoring is disabled since it is mostly not needed
|
||||
# if you don't have latency issues, and collecting data has a performance
|
||||
# impact, that while very small, can be measured under big load. Latency
|
||||
# monitoring can easily be enalbed at runtime using the command
|
||||
# "CONFIG SET latency-monitor-threshold <milliseconds>" if needed.
|
||||
latency-monitor-threshold 0
|
||||
|
||||
############################# Event notification ##############################
|
||||
|
||||
# Redis can notify Pub/Sub clients about events happening in the key space.
|
||||
# This feature is documented at http://redis.io/topics/keyspace-events
|
||||
# This feature is documented at http://redis.io/topics/notifications
|
||||
#
|
||||
# For instance if keyspace events notification is enabled, and a client
|
||||
# performs a DEL operation on key "foo" stored in the Database 0, two
|
||||
|
||||
@@ -4,6 +4,28 @@
|
||||
# The port that this sentinel instance will run on
|
||||
port 26379
|
||||
|
||||
# sentinel announce-ip <ip>
|
||||
# sentinel announce-port <port>
|
||||
#
|
||||
# The above two configuration directives are useful in environments where,
|
||||
# because of NAT, Sentinel is reachable from outside via a non-local address.
|
||||
#
|
||||
# When announce-ip is provided, the Sentinel will claim the specified IP address
|
||||
# in HELLO messages used to gossip its presence, instead of auto-detecting the
|
||||
# local address as it usually does.
|
||||
#
|
||||
# Similarly when announce-port is provided and is valid and non-zero, Sentinel
|
||||
# will announce the specified TCP port.
|
||||
#
|
||||
# The two options don't need to be used together, if only announce-ip is
|
||||
# provided, the Sentinel will announce the specified IP and the server port
|
||||
# as specified by the "port" option. If only announce-port is provided, the
|
||||
# Sentinel will announce the auto-detected local IP and the specified port.
|
||||
#
|
||||
# Example:
|
||||
#
|
||||
# sentinel announce-ip 1.2.3.4
|
||||
|
||||
# dir <working-directory>
|
||||
# Every long running process should have a well-defined working directory.
|
||||
# For Redis Sentinel to chdir to /tmp at startup is the simplest thing
|
||||
|
||||
+11
-5
@@ -19,7 +19,7 @@ DEPENDENCY_TARGETS=hiredis linenoise lua
|
||||
|
||||
# Default settings
|
||||
STD=-std=c99 -pedantic
|
||||
WARN=-Wall
|
||||
WARN=-Wall -W
|
||||
OPT=$(OPTIMIZATION)
|
||||
|
||||
PREFIX?=/usr/local
|
||||
@@ -58,17 +58,23 @@ ifeq ($(uname_S),SunOS)
|
||||
# SunOS
|
||||
INSTALL=cp -pf
|
||||
FINAL_CFLAGS+= -D__EXTENSIONS__ -D_XPG6
|
||||
FINAL_LIBS+= -ldl -lnsl -lsocket -lpthread
|
||||
FINAL_LIBS+= -ldl -lnsl -lsocket -lresolv -lpthread
|
||||
else
|
||||
ifeq ($(uname_S),Darwin)
|
||||
# Darwin (nothing to do)
|
||||
else
|
||||
ifeq ($(uname_S),AIX)
|
||||
# AIX
|
||||
FINAL_LDFLAGS+= -Wl,-bexpall
|
||||
FINAL_LIBS+= -pthread -lcrypt -lbsd
|
||||
|
||||
else
|
||||
# All the other OSes (notably Linux)
|
||||
FINAL_LDFLAGS+= -rdynamic
|
||||
FINAL_LIBS+= -pthread
|
||||
endif
|
||||
endif
|
||||
|
||||
endif
|
||||
# Include paths to dependencies
|
||||
FINAL_CFLAGS+= -I../deps/hiredis -I../deps/linenoise -I../deps/lua/src
|
||||
|
||||
@@ -107,7 +113,7 @@ endif
|
||||
|
||||
REDIS_SERVER_NAME=redis-server
|
||||
REDIS_SENTINEL_NAME=redis-sentinel
|
||||
REDIS_SERVER_OBJ=adlist.o ae.o anet.o dict.o redis.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o migrate.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crc64.o bitops.o sentinel.o notify.o setproctitle.o hyperloglog.o
|
||||
REDIS_SERVER_OBJ=adlist.o ae.o anet.o dict.o redis.o sds.o zmalloc.o lzf_c.o lzf_d.o pqsort.o zipmap.o sha1.o ziplist.o release.o networking.o util.o object.o db.o replication.o rdb.o t_string.o t_list.o t_set.o t_zset.o t_hash.o config.o aof.o pubsub.o multi.o debug.o sort.o intset.o syncio.o migrate.o endianconv.o slowlog.o scripting.o bio.o rio.o rand.o memtest.o crc64.o bitops.o sentinel.o notify.o setproctitle.o hyperloglog.o latency.o sparkline.o
|
||||
REDIS_CLI_NAME=redis-cli
|
||||
REDIS_CLI_OBJ=anet.o sds.o adlist.o redis-cli.o zmalloc.o release.o anet.o ae.o crc64.o
|
||||
REDIS_BENCHMARK_NAME=redis-benchmark
|
||||
@@ -119,7 +125,7 @@ REDIS_CHECK_AOF_OBJ=redis-check-aof.o
|
||||
|
||||
all: $(REDIS_SERVER_NAME) $(REDIS_SENTINEL_NAME) $(REDIS_CLI_NAME) $(REDIS_BENCHMARK_NAME) $(REDIS_CHECK_DUMP_NAME) $(REDIS_CHECK_AOF_NAME)
|
||||
@echo ""
|
||||
@echo "Hint: To run 'make test' is a good idea ;)"
|
||||
@echo "Hint: It's a good idea to run 'make test' ;)"
|
||||
@echo ""
|
||||
|
||||
.PHONY: all
|
||||
|
||||
+47
-34
@@ -7,58 +7,65 @@ ae_select.o: ae_select.c
|
||||
anet.o: anet.c fmacros.h anet.h
|
||||
aof.o: aof.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \
|
||||
../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \
|
||||
ziplist.h intset.h version.h util.h rdb.h rio.h bio.h
|
||||
ziplist.h intset.h version.h util.h latency.h sparkline.h rdb.h rio.h \
|
||||
bio.h
|
||||
bio.o: bio.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \
|
||||
../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \
|
||||
ziplist.h intset.h version.h util.h rdb.h rio.h bio.h
|
||||
ziplist.h intset.h version.h util.h latency.h sparkline.h rdb.h rio.h \
|
||||
bio.h
|
||||
bitops.o: bitops.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \
|
||||
../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \
|
||||
ziplist.h intset.h version.h util.h rdb.h rio.h
|
||||
ziplist.h intset.h version.h util.h latency.h sparkline.h rdb.h rio.h
|
||||
config.o: config.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \
|
||||
../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \
|
||||
ziplist.h intset.h version.h util.h rdb.h rio.h
|
||||
ziplist.h intset.h version.h util.h latency.h sparkline.h rdb.h rio.h
|
||||
crc64.o: crc64.c
|
||||
db.o: db.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \
|
||||
../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \
|
||||
ziplist.h intset.h version.h util.h rdb.h rio.h
|
||||
ziplist.h intset.h version.h util.h latency.h sparkline.h rdb.h rio.h
|
||||
debug.o: debug.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \
|
||||
../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \
|
||||
ziplist.h intset.h version.h util.h rdb.h rio.h sha1.h crc64.h bio.h
|
||||
ziplist.h intset.h version.h util.h latency.h sparkline.h rdb.h rio.h \
|
||||
sha1.h crc64.h bio.h
|
||||
dict.o: dict.c fmacros.h dict.h zmalloc.h redisassert.h
|
||||
endianconv.o: endianconv.c
|
||||
hyperloglog.o: hyperloglog.c redis.h fmacros.h config.h \
|
||||
../deps/lua/src/lua.h ../deps/lua/src/luaconf.h ae.h sds.h dict.h \
|
||||
adlist.h zmalloc.h anet.h ziplist.h intset.h version.h util.h rdb.h \
|
||||
rio.h
|
||||
../deps/lua/src/lua.h ../deps/lua/src/luaconf.h ae.h sds.h dict.h \
|
||||
adlist.h zmalloc.h anet.h ziplist.h intset.h version.h util.h \
|
||||
latency.h sparkline.h rdb.h rio.h
|
||||
intset.o: intset.c intset.h zmalloc.h endianconv.h config.h
|
||||
latency.o: latency.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \
|
||||
../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \
|
||||
ziplist.h intset.h version.h util.h latency.h sparkline.h rdb.h rio.h
|
||||
lzf_c.o: lzf_c.c lzfP.h
|
||||
lzf_d.o: lzf_d.c lzfP.h
|
||||
memtest.o: memtest.c config.h
|
||||
migrate.o: migrate.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \
|
||||
../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \
|
||||
ziplist.h intset.h version.h util.h rdb.h rio.h endianconv.h
|
||||
ziplist.h intset.h version.h util.h latency.h sparkline.h rdb.h rio.h \
|
||||
endianconv.h
|
||||
multi.o: multi.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \
|
||||
../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \
|
||||
ziplist.h intset.h version.h util.h rdb.h rio.h
|
||||
ziplist.h intset.h version.h util.h latency.h sparkline.h rdb.h rio.h
|
||||
networking.o: networking.c redis.h fmacros.h config.h \
|
||||
../deps/lua/src/lua.h ../deps/lua/src/luaconf.h ae.h sds.h dict.h \
|
||||
adlist.h zmalloc.h anet.h ziplist.h intset.h version.h util.h rdb.h \
|
||||
rio.h
|
||||
adlist.h zmalloc.h anet.h ziplist.h intset.h version.h util.h \
|
||||
latency.h sparkline.h rdb.h rio.h
|
||||
notify.o: notify.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \
|
||||
../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \
|
||||
ziplist.h intset.h version.h util.h rdb.h rio.h
|
||||
ziplist.h intset.h version.h util.h latency.h sparkline.h rdb.h rio.h
|
||||
object.o: object.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \
|
||||
../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \
|
||||
ziplist.h intset.h version.h util.h rdb.h rio.h
|
||||
ziplist.h intset.h version.h util.h latency.h sparkline.h rdb.h rio.h
|
||||
pqsort.o: pqsort.c
|
||||
pubsub.o: pubsub.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \
|
||||
../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \
|
||||
ziplist.h intset.h version.h util.h rdb.h rio.h
|
||||
ziplist.h intset.h version.h util.h latency.h sparkline.h rdb.h rio.h
|
||||
rand.o: rand.c
|
||||
rdb.o: rdb.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \
|
||||
../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \
|
||||
ziplist.h intset.h version.h util.h rdb.h rio.h lzf.h zipmap.h \
|
||||
endianconv.h
|
||||
ziplist.h intset.h version.h util.h latency.h sparkline.h rdb.h rio.h \
|
||||
lzf.h zipmap.h endianconv.h
|
||||
redis-benchmark.o: redis-benchmark.c fmacros.h ae.h \
|
||||
../deps/hiredis/hiredis.h sds.h adlist.h zmalloc.h
|
||||
redis-check-aof.o: redis-check-aof.c fmacros.h config.h
|
||||
@@ -67,51 +74,57 @@ redis-cli.o: redis-cli.c fmacros.h version.h ../deps/hiredis/hiredis.h \
|
||||
sds.h zmalloc.h ../deps/linenoise/linenoise.h help.h anet.h ae.h
|
||||
redis.o: redis.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \
|
||||
../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \
|
||||
ziplist.h intset.h version.h util.h rdb.h rio.h slowlog.h bio.h \
|
||||
asciilogo.h
|
||||
ziplist.h intset.h version.h util.h latency.h sparkline.h rdb.h rio.h \
|
||||
slowlog.h bio.h asciilogo.h
|
||||
release.o: release.c release.h version.h crc64.h
|
||||
replication.o: replication.c redis.h fmacros.h config.h \
|
||||
../deps/lua/src/lua.h ../deps/lua/src/luaconf.h ae.h sds.h dict.h \
|
||||
adlist.h zmalloc.h anet.h ziplist.h intset.h version.h util.h rdb.h \
|
||||
rio.h
|
||||
adlist.h zmalloc.h anet.h ziplist.h intset.h version.h util.h \
|
||||
latency.h sparkline.h rdb.h rio.h
|
||||
rio.o: rio.c fmacros.h rio.h sds.h util.h crc64.h config.h redis.h \
|
||||
../deps/lua/src/lua.h ../deps/lua/src/luaconf.h ae.h dict.h adlist.h \
|
||||
zmalloc.h anet.h ziplist.h intset.h version.h rdb.h
|
||||
zmalloc.h anet.h ziplist.h intset.h version.h latency.h sparkline.h \
|
||||
rdb.h
|
||||
scripting.o: scripting.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \
|
||||
../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \
|
||||
ziplist.h intset.h version.h util.h rdb.h rio.h sha1.h rand.h \
|
||||
../deps/lua/src/lauxlib.h ../deps/lua/src/lualib.h
|
||||
ziplist.h intset.h version.h util.h latency.h sparkline.h rdb.h rio.h \
|
||||
sha1.h rand.h ../deps/lua/src/lauxlib.h ../deps/lua/src/lualib.h
|
||||
sds.o: sds.c sds.h zmalloc.h
|
||||
sentinel.o: sentinel.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \
|
||||
../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \
|
||||
ziplist.h intset.h version.h util.h rdb.h rio.h \
|
||||
ziplist.h intset.h version.h util.h latency.h sparkline.h rdb.h rio.h \
|
||||
../deps/hiredis/hiredis.h ../deps/hiredis/async.h
|
||||
setproctitle.o: setproctitle.c
|
||||
sha1.o: sha1.c sha1.h config.h
|
||||
slowlog.o: slowlog.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \
|
||||
../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \
|
||||
ziplist.h intset.h version.h util.h rdb.h rio.h slowlog.h
|
||||
ziplist.h intset.h version.h util.h latency.h sparkline.h rdb.h rio.h \
|
||||
slowlog.h
|
||||
sort.o: sort.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \
|
||||
../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \
|
||||
ziplist.h intset.h version.h util.h rdb.h rio.h pqsort.h
|
||||
ziplist.h intset.h version.h util.h latency.h sparkline.h rdb.h rio.h \
|
||||
pqsort.h
|
||||
sparkline.o: sparkline.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \
|
||||
../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \
|
||||
ziplist.h intset.h version.h util.h latency.h sparkline.h rdb.h rio.h
|
||||
syncio.o: syncio.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \
|
||||
../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \
|
||||
ziplist.h intset.h version.h util.h rdb.h rio.h
|
||||
ziplist.h intset.h version.h util.h latency.h sparkline.h rdb.h rio.h
|
||||
t_hash.o: t_hash.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \
|
||||
../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \
|
||||
ziplist.h intset.h version.h util.h rdb.h rio.h
|
||||
ziplist.h intset.h version.h util.h latency.h sparkline.h rdb.h rio.h
|
||||
t_list.o: t_list.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \
|
||||
../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \
|
||||
ziplist.h intset.h version.h util.h rdb.h rio.h
|
||||
ziplist.h intset.h version.h util.h latency.h sparkline.h rdb.h rio.h
|
||||
t_set.o: t_set.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \
|
||||
../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \
|
||||
ziplist.h intset.h version.h util.h rdb.h rio.h
|
||||
ziplist.h intset.h version.h util.h latency.h sparkline.h rdb.h rio.h
|
||||
t_string.o: t_string.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \
|
||||
../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \
|
||||
ziplist.h intset.h version.h util.h rdb.h rio.h
|
||||
ziplist.h intset.h version.h util.h latency.h sparkline.h rdb.h rio.h
|
||||
t_zset.o: t_zset.c redis.h fmacros.h config.h ../deps/lua/src/lua.h \
|
||||
../deps/lua/src/luaconf.h ae.h sds.h dict.h adlist.h zmalloc.h anet.h \
|
||||
ziplist.h intset.h version.h util.h rdb.h rio.h
|
||||
ziplist.h intset.h version.h util.h latency.h sparkline.h rdb.h rio.h
|
||||
util.o: util.c fmacros.h util.h sds.h
|
||||
ziplist.o: ziplist.c zmalloc.h util.h sds.h ziplist.h endianconv.h \
|
||||
config.h redisassert.h
|
||||
|
||||
+1
-1
@@ -178,7 +178,7 @@ void listDelNode(list *list, listNode *node)
|
||||
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;
|
||||
|
||||
@@ -156,8 +156,9 @@ void aeDeleteFileEvent(aeEventLoop *eventLoop, int fd, int mask)
|
||||
{
|
||||
if (fd >= eventLoop->setsize) return;
|
||||
aeFileEvent *fe = &eventLoop->events[fd];
|
||||
|
||||
if (fe->mask == AE_NONE) return;
|
||||
|
||||
aeApiDelEvent(eventLoop, fd, mask);
|
||||
fe->mask = fe->mask & (~mask);
|
||||
if (fd == eventLoop->maxfd && fe->mask == AE_NONE) {
|
||||
/* Update the max fd */
|
||||
@@ -167,7 +168,6 @@ void aeDeleteFileEvent(aeEventLoop *eventLoop, int fd, int mask)
|
||||
if (eventLoop->events[j].mask != AE_NONE) break;
|
||||
eventLoop->maxfd = j;
|
||||
}
|
||||
aeApiDelEvent(eventLoop, fd, mask);
|
||||
}
|
||||
|
||||
int aeGetFileEvents(aeEventLoop *eventLoop, int fd) {
|
||||
|
||||
+6
-6
@@ -54,7 +54,7 @@ static int aeApiCreate(aeEventLoop *eventLoop) {
|
||||
return -1;
|
||||
}
|
||||
eventLoop->apidata = state;
|
||||
return 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int aeApiResize(aeEventLoop *eventLoop, int setsize) {
|
||||
@@ -75,7 +75,7 @@ static void aeApiFree(aeEventLoop *eventLoop) {
|
||||
static int aeApiAddEvent(aeEventLoop *eventLoop, int fd, int mask) {
|
||||
aeApiState *state = eventLoop->apidata;
|
||||
struct kevent ke;
|
||||
|
||||
|
||||
if (mask & AE_READABLE) {
|
||||
EV_SET(&ke, fd, EVFILT_READ, EV_ADD, 0, 0, NULL);
|
||||
if (kevent(state->kqfd, &ke, 1, NULL, 0, NULL) == -1) return -1;
|
||||
@@ -118,16 +118,16 @@ static int aeApiPoll(aeEventLoop *eventLoop, struct timeval *tvp) {
|
||||
|
||||
if (retval > 0) {
|
||||
int j;
|
||||
|
||||
|
||||
numevents = retval;
|
||||
for(j = 0; j < numevents; j++) {
|
||||
int mask = 0;
|
||||
struct kevent *e = state->events+j;
|
||||
|
||||
|
||||
if (e->filter == EVFILT_READ) mask |= AE_READABLE;
|
||||
if (e->filter == EVFILT_WRITE) mask |= AE_WRITABLE;
|
||||
eventLoop->fired[j].fd = e->ident;
|
||||
eventLoop->fired[j].mask = mask;
|
||||
eventLoop->fired[j].fd = e->ident;
|
||||
eventLoop->fired[j].mask = mask;
|
||||
}
|
||||
}
|
||||
return numevents;
|
||||
|
||||
+3
-1
@@ -117,6 +117,8 @@ int anetKeepAlive(char *err, int fd, int interval)
|
||||
anetSetError(err, "setsockopt TCP_KEEPCNT: %s\n", strerror(errno));
|
||||
return ANET_ERR;
|
||||
}
|
||||
#else
|
||||
((void) interval); /* Avoid unused var warning for non Linux systems. */
|
||||
#endif
|
||||
|
||||
return ANET_OK;
|
||||
@@ -137,7 +139,7 @@ int anetEnableTcpNoDelay(char *err, int fd)
|
||||
return anetSetTcpNoDelay(err, fd, 1);
|
||||
}
|
||||
|
||||
int anetDisableTcpNoDelay(char *err, int fd)
|
||||
int anetDisableTcpNoDelay(char *err, int fd)
|
||||
{
|
||||
return anetSetTcpNoDelay(err, fd, 0);
|
||||
}
|
||||
|
||||
+5
-1
@@ -39,10 +39,14 @@
|
||||
#define ANET_NONE 0
|
||||
#define ANET_IP_ONLY (1<<0)
|
||||
|
||||
#if defined(__sun)
|
||||
#if defined(__sun) || defined(_AIX)
|
||||
#define AF_LOCAL AF_UNIX
|
||||
#endif
|
||||
|
||||
#ifdef _AIX
|
||||
#undef ip_len
|
||||
#endif
|
||||
|
||||
int anetTcpConnect(char *err, char *addr, int port);
|
||||
int anetTcpNonBlockConnect(char *err, char *addr, int port);
|
||||
int anetUnixConnect(char *err, char *path);
|
||||
|
||||
@@ -140,7 +140,7 @@ ssize_t aofRewriteBufferWrite(int fd) {
|
||||
|
||||
if (block->used) {
|
||||
nwritten = write(fd,block->buf,block->used);
|
||||
if (nwritten != block->used) {
|
||||
if (nwritten != (ssize_t)block->used) {
|
||||
if (nwritten == 0) errno = EIO;
|
||||
return -1;
|
||||
}
|
||||
@@ -230,6 +230,7 @@ int startAppendOnly(void) {
|
||||
void flushAppendOnlyFile(int force) {
|
||||
ssize_t nwritten;
|
||||
int sync_in_progress = 0;
|
||||
mstime_t latency;
|
||||
|
||||
if (sdslen(server.aof_buf) == 0) return;
|
||||
|
||||
@@ -257,16 +258,32 @@ void flushAppendOnlyFile(int force) {
|
||||
redisLog(REDIS_NOTICE,"Asynchronous AOF fsync is taking too long (disk is busy?). Writing the AOF buffer without waiting for fsync to complete, this may slow down Redis.");
|
||||
}
|
||||
}
|
||||
/* If you are following this code path, then we are going to write so
|
||||
* set reset the postponed flush sentinel to zero. */
|
||||
server.aof_flush_postponed_start = 0;
|
||||
|
||||
/* We want to perform a single write. This should be guaranteed atomic
|
||||
* at least if the filesystem we are writing is a real physical one.
|
||||
* While this will save us against the server being killed I don't think
|
||||
* there is much to do about the whole server stopping for power problems
|
||||
* or alike */
|
||||
|
||||
latencyStartMonitor(latency);
|
||||
nwritten = write(server.aof_fd,server.aof_buf,sdslen(server.aof_buf));
|
||||
latencyEndMonitor(latency);
|
||||
/* We want to capture different events for delayed writes:
|
||||
* when the delay happens with a pending fsync, or with a saving child
|
||||
* active, and when the above two conditions are missing.
|
||||
* We also use an additional event name to save all samples which is
|
||||
* useful for graphing / monitoring purposes. */
|
||||
if (sync_in_progress) {
|
||||
latencyAddSampleIfNeeded("aof-write-pending-fsync",latency);
|
||||
} else if (server.aof_child_pid != -1 || server.rdb_child_pid != -1) {
|
||||
latencyAddSampleIfNeeded("aof-write-active-child",latency);
|
||||
} else {
|
||||
latencyAddSampleIfNeeded("aof-write-alone",latency);
|
||||
}
|
||||
latencyAddSampleIfNeeded("aof-write",latency);
|
||||
|
||||
/* We performed the write so reset the postponed flush sentinel to zero. */
|
||||
server.aof_flush_postponed_start = 0;
|
||||
|
||||
if (nwritten != (signed)sdslen(server.aof_buf)) {
|
||||
static time_t last_write_error_log = 0;
|
||||
int can_log = 0;
|
||||
@@ -360,7 +377,10 @@ void flushAppendOnlyFile(int force) {
|
||||
if (server.aof_fsync == AOF_FSYNC_ALWAYS) {
|
||||
/* aof_fsync is defined as fdatasync() for Linux in order to avoid
|
||||
* flushing metadata. */
|
||||
latencyStartMonitor(latency);
|
||||
aof_fsync(server.aof_fd); /* Let's try to get this data on the disk */
|
||||
latencyEndMonitor(latency);
|
||||
latencyAddSampleIfNeeded("aof-fsync-always",latency);
|
||||
server.aof_last_fsync = server.unixtime;
|
||||
} else if ((server.aof_fsync == AOF_FSYNC_EVERYSEC &&
|
||||
server.unixtime > server.aof_last_fsync)) {
|
||||
@@ -513,6 +533,14 @@ struct redisClient *createFakeClient(void) {
|
||||
return c;
|
||||
}
|
||||
|
||||
void freeFakeClientArgv(struct redisClient *c) {
|
||||
int j;
|
||||
|
||||
for (j = 0; j < c->argc; j++)
|
||||
decrRefCount(c->argv[j]);
|
||||
zfree(c->argv);
|
||||
}
|
||||
|
||||
void freeFakeClient(struct redisClient *c) {
|
||||
sdsfree(c->querybuf);
|
||||
listRelease(c->reply);
|
||||
@@ -530,6 +558,7 @@ int loadAppendOnlyFile(char *filename) {
|
||||
struct redis_stat sb;
|
||||
int old_aof_state = server.aof_state;
|
||||
long loops = 0;
|
||||
off_t valid_up_to = 0; /* Offset of the latest well-formed command loaded. */
|
||||
|
||||
if (fp && redis_fstat(fileno(fp),&sb) != -1 && sb.st_size == 0) {
|
||||
server.aof_current_size = 0;
|
||||
@@ -570,18 +599,35 @@ int loadAppendOnlyFile(char *filename) {
|
||||
goto readerr;
|
||||
}
|
||||
if (buf[0] != '*') goto fmterr;
|
||||
if (buf[1] == '\0') goto readerr;
|
||||
argc = atoi(buf+1);
|
||||
if (argc < 1) goto fmterr;
|
||||
|
||||
argv = zmalloc(sizeof(robj*)*argc);
|
||||
fakeClient->argc = argc;
|
||||
fakeClient->argv = argv;
|
||||
|
||||
for (j = 0; j < argc; j++) {
|
||||
if (fgets(buf,sizeof(buf),fp) == NULL) goto readerr;
|
||||
if (fgets(buf,sizeof(buf),fp) == NULL) {
|
||||
fakeClient->argc = j; /* Free up to j-1. */
|
||||
freeFakeClientArgv(fakeClient);
|
||||
goto readerr;
|
||||
}
|
||||
if (buf[0] != '$') goto fmterr;
|
||||
len = strtol(buf+1,NULL,10);
|
||||
argsds = sdsnewlen(NULL,len);
|
||||
if (len && fread(argsds,len,1,fp) == 0) goto fmterr;
|
||||
if (len && fread(argsds,len,1,fp) == 0) {
|
||||
sdsfree(argsds);
|
||||
fakeClient->argc = j; /* Free up to j-1. */
|
||||
freeFakeClientArgv(fakeClient);
|
||||
goto readerr;
|
||||
}
|
||||
argv[j] = createObject(REDIS_STRING,argsds);
|
||||
if (fread(buf,2,1,fp) == 0) goto fmterr; /* discard CRLF */
|
||||
if (fread(buf,2,1,fp) == 0) {
|
||||
fakeClient->argc = j+1; /* Free up to j. */
|
||||
freeFakeClientArgv(fakeClient);
|
||||
goto readerr; /* discard CRLF */
|
||||
}
|
||||
}
|
||||
|
||||
/* Command lookup */
|
||||
@@ -590,9 +636,8 @@ int loadAppendOnlyFile(char *filename) {
|
||||
redisLog(REDIS_WARNING,"Unknown command '%s' reading the append only file", (char*)argv[0]->ptr);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/* Run the command in the context of a fake client */
|
||||
fakeClient->argc = argc;
|
||||
fakeClient->argv = argv;
|
||||
cmd->proc(fakeClient);
|
||||
|
||||
/* The fake client should not have a reply */
|
||||
@@ -602,15 +647,15 @@ int loadAppendOnlyFile(char *filename) {
|
||||
|
||||
/* Clean up. Command code may have changed argv/argc so we use the
|
||||
* argv/argc of the client instead of the local variables. */
|
||||
for (j = 0; j < fakeClient->argc; j++)
|
||||
decrRefCount(fakeClient->argv[j]);
|
||||
zfree(fakeClient->argv);
|
||||
freeFakeClientArgv(fakeClient);
|
||||
if (server.aof_load_truncated) valid_up_to = ftello(fp);
|
||||
}
|
||||
|
||||
/* This point can only be reached when EOF is reached without errors.
|
||||
* If the client is in the middle of a MULTI/EXEC, log error and quit. */
|
||||
if (fakeClient->flags & REDIS_MULTI) goto readerr;
|
||||
if (fakeClient->flags & REDIS_MULTI) goto uxeof;
|
||||
|
||||
loaded_ok: /* DB loaded, cleanup and return REDIS_OK to the caller. */
|
||||
fclose(fp);
|
||||
freeFakeClient(fakeClient);
|
||||
server.aof_state = old_aof_state;
|
||||
@@ -619,14 +664,41 @@ int loadAppendOnlyFile(char *filename) {
|
||||
server.aof_rewrite_base_size = server.aof_current_size;
|
||||
return REDIS_OK;
|
||||
|
||||
readerr:
|
||||
if (feof(fp)) {
|
||||
redisLog(REDIS_WARNING,"Unexpected end of file reading the append only file");
|
||||
} else {
|
||||
readerr: /* Read error. If feof(fp) is true, fall through to unexpected EOF. */
|
||||
if (!feof(fp)) {
|
||||
redisLog(REDIS_WARNING,"Unrecoverable error reading the append only file: %s", strerror(errno));
|
||||
exit(1);
|
||||
}
|
||||
|
||||
uxeof: /* Unexpected AOF end of file. */
|
||||
if (server.aof_load_truncated) {
|
||||
redisLog(REDIS_WARNING,"!!! Warning: short read while loading the AOF file !!!");
|
||||
redisLog(REDIS_WARNING,"!!! Truncating the AOF at offset %llu !!!",
|
||||
(unsigned long long) valid_up_to);
|
||||
if (valid_up_to == -1 || truncate(filename,valid_up_to) == -1) {
|
||||
if (valid_up_to == -1) {
|
||||
redisLog(REDIS_WARNING,"Last valid command offset is invalid");
|
||||
} else {
|
||||
redisLog(REDIS_WARNING,"Error truncating the AOF file: %s",
|
||||
strerror(errno));
|
||||
}
|
||||
} else {
|
||||
/* Make sure the AOF file descriptor points to the end of the
|
||||
* file after the truncate call. */
|
||||
if (server.aof_fd != -1 && lseek(server.aof_fd,0,SEEK_END) == -1) {
|
||||
redisLog(REDIS_WARNING,"Can't seek the end of the AOF file: %s",
|
||||
strerror(errno));
|
||||
} else {
|
||||
redisLog(REDIS_WARNING,
|
||||
"AOF loaded anyway because aof-load-truncated is enabled");
|
||||
goto loaded_ok;
|
||||
}
|
||||
}
|
||||
}
|
||||
redisLog(REDIS_WARNING,"Unexpected end of file reading the append only file. You can: 1) Make a backup of your AOF file, then use ./redis-check-aof --fix <filename>. 2) Alternatively you can set the 'aof-load-truncated' configuration option to yes and restart the server.");
|
||||
exit(1);
|
||||
fmterr:
|
||||
|
||||
fmterr: /* Format error. */
|
||||
redisLog(REDIS_WARNING,"Bad file format reading the append only file: make a backup of your AOF file, then use ./redis-check-aof --fix <filename>");
|
||||
exit(1);
|
||||
}
|
||||
@@ -970,7 +1042,7 @@ int rewriteAppendOnlyFile(char *filename) {
|
||||
|
||||
/* Make sure data will not remain on the OS's output buffers */
|
||||
if (fflush(fp) == EOF) goto werr;
|
||||
if (aof_fsync(fileno(fp)) == -1) goto werr;
|
||||
if (fsync(fileno(fp)) == -1) goto werr;
|
||||
if (fclose(fp) == EOF) goto werr;
|
||||
|
||||
/* Use RENAME to make sure the DB file is changed atomically only
|
||||
@@ -1031,6 +1103,8 @@ int rewriteAppendOnlyFileBackground(void) {
|
||||
} else {
|
||||
/* Parent */
|
||||
server.stat_fork_time = ustime()-start;
|
||||
server.stat_fork_rate = (double) zmalloc_used_memory() * 1000000 / server.stat_fork_time / (1024*1024*1024); /* GB per second. */
|
||||
latencyAddSampleIfNeeded("fork",server.stat_fork_time/1000);
|
||||
if (childpid == -1) {
|
||||
redisLog(REDIS_WARNING,
|
||||
"Can't rewrite append only file in background: fork: %s",
|
||||
@@ -1074,19 +1148,23 @@ void aofRemoveTempFile(pid_t childpid) {
|
||||
unlink(tmpfile);
|
||||
}
|
||||
|
||||
/* Update the server.aof_current_size filed explicitly using stat(2)
|
||||
/* Update the server.aof_current_size field explicitly using stat(2)
|
||||
* to check the size of the file. This is useful after a rewrite or after
|
||||
* a restart, normally the size is updated just adding the write length
|
||||
* to the current length, that is much faster. */
|
||||
void aofUpdateCurrentSize(void) {
|
||||
struct redis_stat sb;
|
||||
mstime_t latency;
|
||||
|
||||
latencyStartMonitor(latency);
|
||||
if (redis_fstat(server.aof_fd,&sb) == -1) {
|
||||
redisLog(REDIS_WARNING,"Unable to obtain the AOF file length. stat: %s",
|
||||
strerror(errno));
|
||||
} else {
|
||||
server.aof_current_size = sb.st_size;
|
||||
}
|
||||
latencyEndMonitor(latency);
|
||||
latencyAddSampleIfNeeded("aof-fstat",latency);
|
||||
}
|
||||
|
||||
/* A background append only file rewriting (BGREWRITEAOF) terminated its work.
|
||||
@@ -1096,12 +1174,14 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
|
||||
int newfd, oldfd;
|
||||
char tmpfile[256];
|
||||
long long now = ustime();
|
||||
mstime_t latency;
|
||||
|
||||
redisLog(REDIS_NOTICE,
|
||||
"Background AOF rewrite terminated with success");
|
||||
|
||||
/* Flush the differences accumulated by the parent to the
|
||||
* rewritten AOF. */
|
||||
latencyStartMonitor(latency);
|
||||
snprintf(tmpfile,256,"temp-rewriteaof-bg-%d.aof",
|
||||
(int)server.aof_child_pid);
|
||||
newfd = open(tmpfile,O_WRONLY|O_APPEND);
|
||||
@@ -1117,6 +1197,8 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
|
||||
close(newfd);
|
||||
goto cleanup;
|
||||
}
|
||||
latencyEndMonitor(latency);
|
||||
latencyAddSampleIfNeeded("aof-rewrite-diff-write",latency);
|
||||
|
||||
redisLog(REDIS_NOTICE,
|
||||
"Parent diff successfully flushed to the rewritten AOF (%lu bytes)", aofRewriteBufferSize());
|
||||
@@ -1162,6 +1244,7 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
|
||||
|
||||
/* Rename the temporary file. This will not unlink the target file if
|
||||
* it exists, because we reference it with "oldfd". */
|
||||
latencyStartMonitor(latency);
|
||||
if (rename(tmpfile,server.aof_filename) == -1) {
|
||||
redisLog(REDIS_WARNING,
|
||||
"Error trying to rename the temporary AOF file: %s", strerror(errno));
|
||||
@@ -1169,6 +1252,8 @@ void backgroundRewriteDoneHandler(int exitcode, int bysignal) {
|
||||
if (oldfd != -1) close(oldfd);
|
||||
goto cleanup;
|
||||
}
|
||||
latencyEndMonitor(latency);
|
||||
latencyAddSampleIfNeeded("aof-rename",latency);
|
||||
|
||||
if (server.aof_fd == -1) {
|
||||
/* AOF disabled, we don't need to set the AOF file descriptor
|
||||
|
||||
+11
-9
@@ -107,12 +107,12 @@ size_t redisPopcount(void *s, long count) {
|
||||
* no zero bit is found, it returns count*8 assuming the string is zero
|
||||
* padded on the right. However if 'bit' is 1 it is possible that there is
|
||||
* not a single set bit in the bitmap. In this special case -1 is returned. */
|
||||
long redisBitpos(void *s, long count, int bit) {
|
||||
long redisBitpos(void *s, unsigned long count, int bit) {
|
||||
unsigned long *l;
|
||||
unsigned char *c;
|
||||
unsigned long skipval, word = 0, one;
|
||||
long pos = 0; /* Position of bit, to return to the caller. */
|
||||
int j;
|
||||
unsigned long j;
|
||||
|
||||
/* Process whole words first, seeking for first word that is not
|
||||
* all ones or all zeros respectively if we are lookig for zeros
|
||||
@@ -276,11 +276,12 @@ void getbitCommand(redisClient *c) {
|
||||
void bitopCommand(redisClient *c) {
|
||||
char *opname = c->argv[1]->ptr;
|
||||
robj *o, *targetkey = c->argv[2];
|
||||
long op, j, numkeys;
|
||||
unsigned long op, j, numkeys;
|
||||
robj **objects; /* Array of source objects. */
|
||||
unsigned char **src; /* Array of source strings pointers. */
|
||||
long *len, maxlen = 0; /* Array of length of src strings, and max len. */
|
||||
long minlen = 0; /* Min len among the input keys. */
|
||||
unsigned long *len, maxlen = 0; /* Array of length of src strings,
|
||||
and max len. */
|
||||
unsigned long minlen = 0; /* Min len among the input keys. */
|
||||
unsigned char *res = NULL; /* Resulting string. */
|
||||
|
||||
/* Parse the operation name. */
|
||||
@@ -320,9 +321,10 @@ void bitopCommand(redisClient *c) {
|
||||
}
|
||||
/* Return an error if one of the keys is not a string. */
|
||||
if (checkType(c,o,REDIS_STRING)) {
|
||||
for (j = j-1; j >= 0; j--) {
|
||||
if (objects[j])
|
||||
decrRefCount(objects[j]);
|
||||
unsigned long i;
|
||||
for (i = 0; i < j; i++) {
|
||||
if (objects[i])
|
||||
decrRefCount(objects[i]);
|
||||
}
|
||||
zfree(src);
|
||||
zfree(len);
|
||||
@@ -340,7 +342,7 @@ void bitopCommand(redisClient *c) {
|
||||
if (maxlen) {
|
||||
res = (unsigned char*) sdsnewlen(NULL,maxlen);
|
||||
unsigned char output, byte;
|
||||
long i;
|
||||
unsigned long i;
|
||||
|
||||
/* Fast path: as far as we have data for all the input bitmaps we
|
||||
* can take a fast path that performs much better than the
|
||||
|
||||
+33
-6
@@ -72,7 +72,7 @@ void appendServerSaveParams(time_t seconds, int changes) {
|
||||
server.saveparamslen++;
|
||||
}
|
||||
|
||||
void resetServerSaveParams() {
|
||||
void resetServerSaveParams(void) {
|
||||
zfree(server.saveparams);
|
||||
server.saveparams = NULL;
|
||||
server.saveparamslen = 0;
|
||||
@@ -355,7 +355,12 @@ void loadServerConfigFromString(char *config) {
|
||||
} else if (!strcasecmp(argv[0],"aof-rewrite-incremental-fsync") &&
|
||||
argc == 2)
|
||||
{
|
||||
if ((server.aof_rewrite_incremental_fsync = yesnotoi(argv[1])) == -1) {
|
||||
if ((server.aof_rewrite_incremental_fsync =
|
||||
yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"aof-load-truncated") && argc == 2) {
|
||||
if ((server.aof_load_truncated = yesnotoi(argv[1])) == -1) {
|
||||
err = "argument must be 'yes' or 'no'"; goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"requirepass") && argc == 2) {
|
||||
@@ -420,6 +425,14 @@ void loadServerConfigFromString(char *config) {
|
||||
argc == 2)
|
||||
{
|
||||
server.slowlog_log_slower_than = strtoll(argv[1],NULL,10);
|
||||
} else if (!strcasecmp(argv[0],"latency-monitor-threshold") &&
|
||||
argc == 2)
|
||||
{
|
||||
server.latency_monitor_threshold = strtoll(argv[1],NULL,10);
|
||||
if (server.latency_monitor_threshold < 0) {
|
||||
err = "The latency threshold can't be negative";
|
||||
goto loaderr;
|
||||
}
|
||||
} else if (!strcasecmp(argv[0],"slowlog-max-len") && argc == 2) {
|
||||
server.slowlog_max_len = strtoll(argv[1],NULL,10);
|
||||
} else if (!strcasecmp(argv[0],"client-output-buffer-limit") &&
|
||||
@@ -581,7 +594,7 @@ void configSetCommand(redisClient *c) {
|
||||
server.maxclients = orig_value;
|
||||
return;
|
||||
}
|
||||
if (aeGetSetSize(server.el) <
|
||||
if ((unsigned int) aeGetSetSize(server.el) <
|
||||
server.maxclients + REDIS_EVENTLOOP_FDSET_INCR)
|
||||
{
|
||||
if (aeResizeSetSize(server.el,
|
||||
@@ -665,6 +678,11 @@ void configSetCommand(redisClient *c) {
|
||||
|
||||
if (yn == -1) goto badfmt;
|
||||
server.aof_rewrite_incremental_fsync = yn;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"aof-load-truncated")) {
|
||||
int yn = yesnotoi(o->ptr);
|
||||
|
||||
if (yn == -1) goto badfmt;
|
||||
server.aof_load_truncated = yn;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"save")) {
|
||||
int vlen, j;
|
||||
sds *v = sdssplitlen(o->ptr,sdslen(o->ptr)," ",1,&vlen);
|
||||
@@ -747,6 +765,9 @@ void configSetCommand(redisClient *c) {
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"slowlog-max-len")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
|
||||
server.slowlog_max_len = (unsigned)ll;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"latency-monitor-threshold")) {
|
||||
if (getLongLongFromObject(o,&ll) == REDIS_ERR || ll < 0) goto badfmt;
|
||||
server.latency_monitor_threshold = ll;
|
||||
} else if (!strcasecmp(c->argv[2]->ptr,"loglevel")) {
|
||||
if (!strcasecmp(o->ptr,"warning")) {
|
||||
server.verbosity = REDIS_WARNING;
|
||||
@@ -944,6 +965,8 @@ void configGetCommand(redisClient *c) {
|
||||
config_get_numerical_field("lua-time-limit",server.lua_time_limit);
|
||||
config_get_numerical_field("slowlog-log-slower-than",
|
||||
server.slowlog_log_slower_than);
|
||||
config_get_numerical_field("latency-monitor-threshold",
|
||||
server.latency_monitor_threshold);
|
||||
config_get_numerical_field("slowlog-max-len",
|
||||
server.slowlog_max_len);
|
||||
config_get_numerical_field("port",server.port);
|
||||
@@ -977,6 +1000,8 @@ void configGetCommand(redisClient *c) {
|
||||
server.repl_disable_tcp_nodelay);
|
||||
config_get_bool_field("aof-rewrite-incremental-fsync",
|
||||
server.aof_rewrite_incremental_fsync);
|
||||
config_get_bool_field("aof-load-truncated",
|
||||
server.aof_load_truncated);
|
||||
|
||||
/* Everything we can't handle with macros follows. */
|
||||
|
||||
@@ -1255,7 +1280,7 @@ struct rewriteConfigState *rewriteConfigReadOldFile(char *path) {
|
||||
* "force" is non-zero, the line is appended to the configuration file.
|
||||
* Usually "force" is true when an option has not its default value, so it
|
||||
* must be rewritten even if not present previously.
|
||||
*
|
||||
*
|
||||
* The first time a line is appended into a configuration file, a comment
|
||||
* is added to show that starting from that point the config file was generated
|
||||
* by CONFIG REWRITE.
|
||||
@@ -1347,7 +1372,7 @@ void rewriteConfigStringOption(struct rewriteConfigState *state, char *option, c
|
||||
return;
|
||||
}
|
||||
|
||||
/* Compare the strings as sds strings to have a binary safe comparison. */
|
||||
/* Set force to zero if the value is set to its default. */
|
||||
if (defvalue && strcmp(value,defvalue) == 0) force = 0;
|
||||
|
||||
line = sdsnew(option);
|
||||
@@ -1381,7 +1406,7 @@ void rewriteConfigEnumOption(struct rewriteConfigState *state, char *option, int
|
||||
char *enum_name, *matching_name = NULL;
|
||||
int enum_val, def_val, force;
|
||||
sds line;
|
||||
|
||||
|
||||
va_start(ap, value);
|
||||
while(1) {
|
||||
enum_name = va_arg(ap,char*);
|
||||
@@ -1724,6 +1749,7 @@ int rewriteConfig(char *path) {
|
||||
rewriteConfigBytesOption(state,"auto-aof-rewrite-min-size",server.aof_rewrite_min_size,REDIS_AOF_REWRITE_MIN_SIZE);
|
||||
rewriteConfigNumericalOption(state,"lua-time-limit",server.lua_time_limit,REDIS_LUA_TIME_LIMIT);
|
||||
rewriteConfigNumericalOption(state,"slowlog-log-slower-than",server.slowlog_log_slower_than,REDIS_SLOWLOG_LOG_SLOWER_THAN);
|
||||
rewriteConfigNumericalOption(state,"latency-monitor-threshold",server.latency_monitor_threshold,REDIS_DEFAULT_LATENCY_MONITOR_THRESHOLD);
|
||||
rewriteConfigNumericalOption(state,"slowlog-max-len",server.slowlog_max_len,REDIS_SLOWLOG_MAX_LEN);
|
||||
rewriteConfigNotifykeyspaceeventsOption(state);
|
||||
rewriteConfigNumericalOption(state,"hash-max-ziplist-entries",server.hash_max_ziplist_entries,REDIS_HASH_MAX_ZIPLIST_ENTRIES);
|
||||
@@ -1738,6 +1764,7 @@ int rewriteConfig(char *path) {
|
||||
rewriteConfigClientoutputbufferlimitOption(state);
|
||||
rewriteConfigNumericalOption(state,"hz",server.hz,REDIS_DEFAULT_HZ);
|
||||
rewriteConfigYesNoOption(state,"aof-rewrite-incremental-fsync",server.aof_rewrite_incremental_fsync,REDIS_DEFAULT_AOF_REWRITE_INCREMENTAL_FSYNC);
|
||||
rewriteConfigYesNoOption(state,"aof-load-truncated",server.aof_load_truncated,REDIS_DEFAULT_AOF_LOAD_TRUNCATED);
|
||||
if (server.sentinel_mode) rewriteConfigSentinelOption(state);
|
||||
|
||||
/* Step 3: remove all the orphaned lines in the old file, that is, lines
|
||||
|
||||
+7
-2
@@ -185,11 +185,16 @@ void setproctitle(const char *fmt, ...);
|
||||
#error "Undefined or invalid BYTE_ORDER"
|
||||
#endif
|
||||
|
||||
#if (__i386 || __amd64) && __GNUC__
|
||||
#if (__i386 || __amd64 || __powerpc__) && __GNUC__
|
||||
#define GNUC_VERSION (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__)
|
||||
#if (GNUC_VERSION >= 40100) || defined(__clang__)
|
||||
#if defined(__clang__)
|
||||
#define HAVE_ATOMIC
|
||||
#endif
|
||||
#if (defined(__GLIBC__) && defined(__GLIBC_PREREQ))
|
||||
#if (GNUC_VERSION >= 40100 && __GLIBC_PREREQ(2, 6))
|
||||
#define HAVE_ATOMIC
|
||||
#endif
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
@@ -103,7 +103,7 @@ void dbAdd(redisDb *db, robj *key, robj *val) {
|
||||
* The program is aborted if the key was not already present. */
|
||||
void dbOverwrite(redisDb *db, robj *key, robj *val) {
|
||||
struct dictEntry *de = dictFind(db->dict,key->ptr);
|
||||
|
||||
|
||||
redisAssertWithInfo(NULL,key,de != NULL);
|
||||
dictReplace(db->dict, key->ptr, val);
|
||||
}
|
||||
@@ -411,9 +411,7 @@ int parseScanCursorOrReply(redisClient *c, robj *o, unsigned long *cursor) {
|
||||
* In the case of a Hash object the function returns both the field and value
|
||||
* of every element on the Hash. */
|
||||
void scanGenericCommand(redisClient *c, robj *o, unsigned long cursor) {
|
||||
int rv;
|
||||
int i, j;
|
||||
char buf[REDIS_LONGSTR_SIZE];
|
||||
list *keys = listCreate();
|
||||
listNode *node, *nextnode;
|
||||
long count = 10;
|
||||
@@ -485,6 +483,11 @@ void scanGenericCommand(redisClient *c, robj *o, unsigned long cursor) {
|
||||
|
||||
if (ht) {
|
||||
void *privdata[2];
|
||||
/* We set the max number of iterations to ten times the specified
|
||||
* COUNT, so if the hash table is in a pathological state (very
|
||||
* sparsely populated) we avoid to block too much time at the cost
|
||||
* of returning no or very few elements. */
|
||||
long maxiterations = count*10;
|
||||
|
||||
/* We pass two pointers to the callback: the list to which it will
|
||||
* add new elements, and the object containing the dictionary so that
|
||||
@@ -493,7 +496,9 @@ void scanGenericCommand(redisClient *c, robj *o, unsigned long cursor) {
|
||||
privdata[1] = o;
|
||||
do {
|
||||
cursor = dictScan(ht, cursor, scanCallback, privdata);
|
||||
} while (cursor && listLength(keys) < count);
|
||||
} while (cursor &&
|
||||
maxiterations-- &&
|
||||
listLength(keys) < (unsigned long)count);
|
||||
} else if (o->type == REDIS_SET) {
|
||||
int pos = 0;
|
||||
int64_t ll;
|
||||
@@ -567,9 +572,7 @@ void scanGenericCommand(redisClient *c, robj *o, unsigned long cursor) {
|
||||
|
||||
/* Step 4: Reply to the client. */
|
||||
addReplyMultiBulkLen(c, 2);
|
||||
rv = snprintf(buf, sizeof(buf), "%lu", cursor);
|
||||
redisAssert(rv < sizeof(buf));
|
||||
addReplyBulkCBuffer(c, buf, rv);
|
||||
addReplyBulkLongLong(c,cursor);
|
||||
|
||||
addReplyMultiBulkLen(c, listLength(keys));
|
||||
while ((node = listFirst(keys)) != NULL) {
|
||||
@@ -697,11 +700,16 @@ void moveCommand(redisClient *c) {
|
||||
robj *o;
|
||||
redisDb *src, *dst;
|
||||
int srcid;
|
||||
long long dbid;
|
||||
|
||||
/* Obtain source and target DB pointers */
|
||||
src = c->db;
|
||||
srcid = c->db->id;
|
||||
if (selectDb(c,atoi(c->argv[2]->ptr)) == REDIS_ERR) {
|
||||
|
||||
if (getLongLongFromObject(c->argv[2],&dbid) == REDIS_ERR ||
|
||||
dbid < INT_MIN || dbid > INT_MAX ||
|
||||
selectDb(c,dbid) == REDIS_ERR)
|
||||
{
|
||||
addReply(c,shared.outofrangeerr);
|
||||
return;
|
||||
}
|
||||
@@ -816,7 +824,7 @@ int expireIfNeeded(redisDb *db, robj *key) {
|
||||
* the slave key expiration is controlled by the master that will
|
||||
* send us synthesized DEL operations for expired keys.
|
||||
*
|
||||
* Still we try to return the right information to the caller,
|
||||
* Still we try to return the right information to the caller,
|
||||
* that is, 0 if we think the key should be still valid, 1 if
|
||||
* we think the key is expired at this time. */
|
||||
if (server.masterhost != NULL) return now > when;
|
||||
|
||||
+2
-8
@@ -79,12 +79,6 @@ unsigned int dictIntHashFunction(unsigned int key)
|
||||
return key;
|
||||
}
|
||||
|
||||
/* Identity hash function for integer keys */
|
||||
unsigned int dictIdentityHashFunction(unsigned int key)
|
||||
{
|
||||
return key;
|
||||
}
|
||||
|
||||
static uint32_t dict_hash_function_seed = 5381;
|
||||
|
||||
void dictSetHashFunctionSeed(uint32_t seed) {
|
||||
@@ -257,7 +251,7 @@ int dictRehash(dict *d, int n) {
|
||||
|
||||
/* Note that rehashidx can't overflow as we are sure there are more
|
||||
* elements because ht[0].used != 0 */
|
||||
assert(d->ht[0].size > (unsigned)d->rehashidx);
|
||||
assert(d->ht[0].size > (unsigned long)d->rehashidx);
|
||||
while(d->ht[0].table[d->rehashidx] == NULL) d->rehashidx++;
|
||||
de = d->ht[0].table[d->rehashidx];
|
||||
/* Move all the keys in this bucket from the old to the new hash HT */
|
||||
@@ -576,7 +570,7 @@ dictEntry *dictNext(dictIterator *iter)
|
||||
iter->fingerprint = dictFingerprint(iter->d);
|
||||
}
|
||||
iter->index++;
|
||||
if (iter->index >= (signed) ht->size) {
|
||||
if (iter->index >= (long) ht->size) {
|
||||
if (dictIsRehashing(iter->d) && iter->table == 0) {
|
||||
iter->table++;
|
||||
iter->index = 0;
|
||||
|
||||
+11
-4
@@ -50,6 +50,7 @@ typedef struct dictEntry {
|
||||
void *val;
|
||||
uint64_t u64;
|
||||
int64_t s64;
|
||||
double d;
|
||||
} v;
|
||||
struct dictEntry *next;
|
||||
} dictEntry;
|
||||
@@ -76,7 +77,7 @@ typedef struct dict {
|
||||
dictType *type;
|
||||
void *privdata;
|
||||
dictht ht[2];
|
||||
int rehashidx; /* rehashing not in progress if rehashidx == -1 */
|
||||
long rehashidx; /* rehashing not in progress if rehashidx == -1 */
|
||||
int iterators; /* number of iterators currently running */
|
||||
} dict;
|
||||
|
||||
@@ -86,9 +87,11 @@ typedef struct dict {
|
||||
* should be called while iterating. */
|
||||
typedef struct dictIterator {
|
||||
dict *d;
|
||||
int table, index, safe;
|
||||
long index;
|
||||
int table, safe;
|
||||
dictEntry *entry, *nextEntry;
|
||||
long long fingerprint; /* unsafe iterator fingerprint for misuse detection */
|
||||
/* unsafe iterator fingerprint for misuse detection. */
|
||||
long long fingerprint;
|
||||
} dictIterator;
|
||||
|
||||
typedef void (dictScanFunction)(void *privdata, const dictEntry *de);
|
||||
@@ -114,6 +117,9 @@ typedef void (dictScanFunction)(void *privdata, const dictEntry *de);
|
||||
#define dictSetUnsignedIntegerVal(entry, _val_) \
|
||||
do { entry->v.u64 = _val_; } while(0)
|
||||
|
||||
#define dictSetDoubleVal(entry, _val_) \
|
||||
do { entry->v.d = _val_; } while(0)
|
||||
|
||||
#define dictFreeKey(d, entry) \
|
||||
if ((d)->type->keyDestructor) \
|
||||
(d)->type->keyDestructor((d)->privdata, (entry)->key)
|
||||
@@ -135,9 +141,10 @@ typedef void (dictScanFunction)(void *privdata, const dictEntry *de);
|
||||
#define dictGetVal(he) ((he)->v.val)
|
||||
#define dictGetSignedIntegerVal(he) ((he)->v.s64)
|
||||
#define dictGetUnsignedIntegerVal(he) ((he)->v.u64)
|
||||
#define dictGetDoubleVal(he) ((he)->v.d)
|
||||
#define dictSlots(d) ((d)->ht[0].size+(d)->ht[1].size)
|
||||
#define dictSize(d) ((d)->ht[0].used+(d)->ht[1].used)
|
||||
#define dictIsRehashing(ht) ((ht)->rehashidx != -1)
|
||||
#define dictIsRehashing(d) ((d)->rehashidx != -1)
|
||||
|
||||
/* API */
|
||||
dict *dictCreate(dictType *type, void *privDataPtr);
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@
|
||||
* 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 __ENDIANCONV_H
|
||||
#define __ENDIANCONV_H
|
||||
|
||||
@@ -36,6 +36,10 @@
|
||||
#define _GNU_SOURCE
|
||||
#endif
|
||||
|
||||
#if defined(_AIX)
|
||||
#define _ALL_SOURCE
|
||||
#endif
|
||||
|
||||
#if defined(__linux__) || defined(__OpenBSD__)
|
||||
#define _XOPEN_SOURCE 700
|
||||
/*
|
||||
@@ -46,6 +50,10 @@
|
||||
#define _XOPEN_SOURCE
|
||||
#endif
|
||||
|
||||
#if defined(__sun)
|
||||
#define _POSIX_C_SOURCE 199506L
|
||||
#endif
|
||||
|
||||
#define _LARGEFILE_SOURCE
|
||||
#define _FILE_OFFSET_BITS 64
|
||||
|
||||
|
||||
+11
-3
@@ -1349,7 +1349,7 @@ void pfmergeCommand(redisClient *c) {
|
||||
* Something that is not easy to test from within the outside. */
|
||||
#define HLL_TEST_CYCLES 1000
|
||||
void pfselftestCommand(redisClient *c) {
|
||||
int j, i;
|
||||
unsigned int j, i;
|
||||
sds bitcounters = sdsnewlen(NULL,HLL_DENSE_SIZE);
|
||||
struct hllhdr *hdr = (struct hllhdr*) bitcounters, *hdr2;
|
||||
robj *o = NULL;
|
||||
@@ -1386,7 +1386,7 @@ void pfselftestCommand(redisClient *c) {
|
||||
* The test adds unique elements and check that the estimated value
|
||||
* is always reasonable bounds.
|
||||
*
|
||||
* We check that the error is smaller than 4 times than the expected
|
||||
* We check that the error is smaller than a few times than the expected
|
||||
* standard error, to make it very unlikely for the test to fail because
|
||||
* of a "bad" run.
|
||||
*
|
||||
@@ -1422,8 +1422,16 @@ void pfselftestCommand(redisClient *c) {
|
||||
/* Check error. */
|
||||
if (j == checkpoint) {
|
||||
int64_t abserr = checkpoint - (int64_t)hllCount(hdr,NULL);
|
||||
uint64_t maxerr = ceil(relerr*6*checkpoint);
|
||||
|
||||
/* Adjust the max error we expect for cardinality 10
|
||||
* since from time to time it is statistically likely to get
|
||||
* much higher error due to collision, resulting into a false
|
||||
* positive. */
|
||||
if (j == 10) maxerr = 1;
|
||||
|
||||
if (abserr < 0) abserr = -abserr;
|
||||
if (abserr > (uint64_t)(relerr*4*checkpoint)) {
|
||||
if (abserr > (int64_t)maxerr) {
|
||||
addReplyErrorFormat(c,
|
||||
"TESTFAILED Too big error. card:%llu abserr:%llu",
|
||||
(unsigned long long) checkpoint,
|
||||
|
||||
+1
-1
@@ -133,7 +133,7 @@ static uint8_t intsetSearch(intset *is, int64_t value, uint32_t *pos) {
|
||||
}
|
||||
|
||||
while(max >= min) {
|
||||
mid = (min+max)/2;
|
||||
mid = ((unsigned int)min + (unsigned int)max) >> 1;
|
||||
cur = _intsetGet(is,mid);
|
||||
if (value > cur) {
|
||||
min = mid+1;
|
||||
|
||||
+578
@@ -0,0 +1,578 @@
|
||||
/* The latency monitor allows to easily observe the sources of latency
|
||||
* in a Redis instance using the LATENCY command. Different latency
|
||||
* sources are monitored, like disk I/O, execution of commands, fork
|
||||
* system call, and so forth.
|
||||
*
|
||||
* ----------------------------------------------------------------------------
|
||||
*
|
||||
* Copyright (c) 2014, 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 "redis.h"
|
||||
|
||||
/* Dictionary type for latency events. */
|
||||
int dictStringKeyCompare(void *privdata, const void *key1, const void *key2) {
|
||||
REDIS_NOTUSED(privdata);
|
||||
return strcmp(key1,key2) == 0;
|
||||
}
|
||||
|
||||
unsigned int dictStringHash(const void *key) {
|
||||
return dictGenHashFunction(key, strlen(key));
|
||||
}
|
||||
|
||||
void dictVanillaFree(void *privdata, void *val);
|
||||
|
||||
dictType latencyTimeSeriesDictType = {
|
||||
dictStringHash, /* hash function */
|
||||
NULL, /* key dup */
|
||||
NULL, /* val dup */
|
||||
dictStringKeyCompare, /* key compare */
|
||||
dictVanillaFree, /* key destructor */
|
||||
dictVanillaFree /* val destructor */
|
||||
};
|
||||
|
||||
/* ---------------------------- Latency API --------------------------------- */
|
||||
|
||||
/* Latency monitor initialization. We just need to create the dictionary
|
||||
* of time series, each time serie is craeted on demand in order to avoid
|
||||
* having a fixed list to maintain. */
|
||||
void latencyMonitorInit(void) {
|
||||
server.latency_events = dictCreate(&latencyTimeSeriesDictType,NULL);
|
||||
}
|
||||
|
||||
/* Add the specified sample to the specified time series "event".
|
||||
* This function is usually called via latencyAddSampleIfNeeded(), that
|
||||
* is a macro that only adds the sample if the latency is higher than
|
||||
* server.latency_monitor_threshold. */
|
||||
void latencyAddSample(char *event, mstime_t latency) {
|
||||
struct latencyTimeSeries *ts = dictFetchValue(server.latency_events,event);
|
||||
time_t now = time(NULL);
|
||||
int prev;
|
||||
|
||||
/* Create the time series if it does not exist. */
|
||||
if (ts == NULL) {
|
||||
ts = zmalloc(sizeof(*ts));
|
||||
ts->idx = 0;
|
||||
ts->max = 0;
|
||||
memset(ts->samples,0,sizeof(ts->samples));
|
||||
dictAdd(server.latency_events,zstrdup(event),ts);
|
||||
}
|
||||
|
||||
/* If the previous sample is in the same second, we update our old sample
|
||||
* if this latency is > of the old one, or just return. */
|
||||
prev = (ts->idx + LATENCY_TS_LEN - 1) % LATENCY_TS_LEN;
|
||||
if (ts->samples[prev].time == now) {
|
||||
if (latency > ts->samples[prev].latency)
|
||||
ts->samples[prev].latency = latency;
|
||||
return;
|
||||
}
|
||||
|
||||
ts->samples[ts->idx].time = time(NULL);
|
||||
ts->samples[ts->idx].latency = latency;
|
||||
if (latency > ts->max) ts->max = latency;
|
||||
|
||||
ts->idx++;
|
||||
if (ts->idx == LATENCY_TS_LEN) ts->idx = 0;
|
||||
}
|
||||
|
||||
/* Reset data for the specified event, or all the events data if 'event' is
|
||||
* NULL.
|
||||
*
|
||||
* Note: this is O(N) even when event_to_reset is not NULL because makes
|
||||
* the code simpler and we have a small fixed max number of events. */
|
||||
int latencyResetEvent(char *event_to_reset) {
|
||||
dictIterator *di;
|
||||
dictEntry *de;
|
||||
int resets = 0;
|
||||
|
||||
di = dictGetSafeIterator(server.latency_events);
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
char *event = dictGetKey(de);
|
||||
|
||||
if (event_to_reset == NULL || strcasecmp(event,event_to_reset) == 0) {
|
||||
dictDelete(server.latency_events, event);
|
||||
resets++;
|
||||
}
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
return resets;
|
||||
}
|
||||
|
||||
/* ------------------------ Latency reporting (doctor) ---------------------- */
|
||||
|
||||
/* Analyze the samples avaialble for a given event and return a structure
|
||||
* populate with different metrics, average, MAD, min, max, and so forth.
|
||||
* Check latency.h definition of struct latenctStat for more info.
|
||||
* If the specified event has no elements the structure is populate with
|
||||
* zero values. */
|
||||
void analyzeLatencyForEvent(char *event, struct latencyStats *ls) {
|
||||
struct latencyTimeSeries *ts = dictFetchValue(server.latency_events,event);
|
||||
int j;
|
||||
uint64_t sum;
|
||||
|
||||
ls->all_time_high = ts ? ts->max : 0;
|
||||
ls->avg = 0;
|
||||
ls->min = 0;
|
||||
ls->max = 0;
|
||||
ls->mad = 0;
|
||||
ls->samples = 0;
|
||||
ls->period = 0;
|
||||
if (!ts) return;
|
||||
|
||||
/* First pass, populate everything but the MAD. */
|
||||
sum = 0;
|
||||
for (j = 0; j < LATENCY_TS_LEN; j++) {
|
||||
if (ts->samples[j].time == 0) continue;
|
||||
ls->samples++;
|
||||
if (ls->samples == 1) {
|
||||
ls->min = ls->max = ts->samples[j].latency;
|
||||
} else {
|
||||
if (ls->min > ts->samples[j].latency)
|
||||
ls->min = ts->samples[j].latency;
|
||||
if (ls->max < ts->samples[j].latency)
|
||||
ls->max = ts->samples[j].latency;
|
||||
}
|
||||
sum += ts->samples[j].latency;
|
||||
|
||||
/* Track the oldest event time in ls->period. */
|
||||
if (ls->period == 0 || ts->samples[j].time < ls->period)
|
||||
ls->period = ts->samples[j].time;
|
||||
}
|
||||
|
||||
/* So far avg is actually the sum of the latencies, and period is
|
||||
* the oldest event time. We need to make the first an average and
|
||||
* the second a range of seconds. */
|
||||
if (ls->samples) {
|
||||
ls->avg = sum / ls->samples;
|
||||
ls->period = time(NULL) - ls->period;
|
||||
if (ls->period == 0) ls->period = 1;
|
||||
}
|
||||
|
||||
/* Second pass, compute MAD. */
|
||||
sum = 0;
|
||||
for (j = 0; j < LATENCY_TS_LEN; j++) {
|
||||
int64_t delta;
|
||||
|
||||
if (ts->samples[j].time == 0) continue;
|
||||
delta = (int64_t)ls->avg - ts->samples[j].latency;
|
||||
if (delta < 0) delta = -delta;
|
||||
sum += delta;
|
||||
}
|
||||
if (ls->samples) ls->mad = sum / ls->samples;
|
||||
}
|
||||
|
||||
/* Create a human readable report of latency events for this Redis instance. */
|
||||
sds createLatencyReport(void) {
|
||||
sds report = sdsempty();
|
||||
int advise_better_vm = 0; /* Better virtual machines. */
|
||||
int advise_slowlog_enabled = 0; /* Enable slowlog. */
|
||||
int advise_slowlog_tuning = 0; /* Reconfigure slowlog. */
|
||||
int advise_slowlog_inspect = 0; /* Check your slowlog. */
|
||||
int advise_disk_contention = 0; /* Try to lower disk contention. */
|
||||
int advise_scheduler = 0; /* Intrinsic latency. */
|
||||
int advise_data_writeback = 0; /* data=writeback. */
|
||||
int advise_no_appendfsync = 0; /* don't fsync during rewrites. */
|
||||
int advise_local_disk = 0; /* Avoid remote disks. */
|
||||
int advise_ssd = 0; /* Use an SSD drive. */
|
||||
int advise_write_load_info = 0; /* Print info about AOF and write load. */
|
||||
int advise_hz = 0; /* Use higher HZ. */
|
||||
int advise_large_objects = 0; /* Deletion of large objects. */
|
||||
int advise_relax_fsync_policy = 0; /* appendfsync always is slow. */
|
||||
int advices = 0;
|
||||
|
||||
/* Return ASAP if the latency engine is disabled and it looks like it
|
||||
* was never enabled so far. */
|
||||
if (dictSize(server.latency_events) == 0 &&
|
||||
server.latency_monitor_threshold == 0)
|
||||
{
|
||||
report = sdscat(report,"I'm sorry, Dave, I can't do that. Latency monitoring is disabled in this Redis instance. You may use \"CONFIG SET latency-monitor-threshold <milliseconds>.\" in order to enable it. If we weren't in a deep space mission I'd suggest to take a look at http://redis.io/topics/latency-monitor.\n");
|
||||
return report;
|
||||
}
|
||||
|
||||
/* Show all the events stats and add for each event some event-related
|
||||
* comment depending on the values. */
|
||||
dictIterator *di;
|
||||
dictEntry *de;
|
||||
int eventnum = 0;
|
||||
|
||||
di = dictGetIterator(server.latency_events);
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
char *event = dictGetKey(de);
|
||||
struct latencyTimeSeries *ts = dictGetVal(de);
|
||||
struct latencyStats ls;
|
||||
|
||||
if (ts == NULL) continue;
|
||||
eventnum++;
|
||||
if (eventnum == 1) {
|
||||
report = sdscat(report,"Dave, I have observed latency spikes in this Redis instance. You don't mind talking about it, do you Dave?\n\n");
|
||||
}
|
||||
analyzeLatencyForEvent(event,&ls);
|
||||
|
||||
report = sdscatprintf(report,
|
||||
"%d. %s: %d latency spikes (average %lums, mean deviation %lums, period %.2f sec). Worst all time event %lums.",
|
||||
eventnum, event,
|
||||
ls.samples,
|
||||
(unsigned long) ls.avg,
|
||||
(unsigned long) ls.mad,
|
||||
(double) ls.period/ls.samples,
|
||||
(unsigned long) ts->max);
|
||||
|
||||
/* Fork */
|
||||
if (!strcasecmp(event,"fork")) {
|
||||
char *fork_quality;
|
||||
if (server.stat_fork_rate < 10) {
|
||||
fork_quality = "terrible";
|
||||
advise_better_vm = 1;
|
||||
advices++;
|
||||
} else if (server.stat_fork_rate < 25) {
|
||||
fork_quality = "poor";
|
||||
advise_better_vm = 1;
|
||||
advices++;
|
||||
} else if (server.stat_fork_rate < 100) {
|
||||
fork_quality = "good";
|
||||
} else {
|
||||
fork_quality = "excellent";
|
||||
}
|
||||
report = sdscatprintf(report,
|
||||
" Fork rate is %.2f GB/sec (%s).", server.stat_fork_rate,
|
||||
fork_quality);
|
||||
}
|
||||
|
||||
/* Potentially commands. */
|
||||
if (!strcasecmp(event,"command")) {
|
||||
if (server.slowlog_log_slower_than == 0) {
|
||||
advise_slowlog_enabled = 1;
|
||||
advices++;
|
||||
} else if (server.slowlog_log_slower_than/1000 >
|
||||
server.latency_monitor_threshold)
|
||||
{
|
||||
advise_slowlog_tuning = 1;
|
||||
advices++;
|
||||
}
|
||||
advise_slowlog_inspect = 1;
|
||||
advise_large_objects = 1;
|
||||
advices += 2;
|
||||
}
|
||||
|
||||
/* fast-command. */
|
||||
if (!strcasecmp(event,"fast-command")) {
|
||||
advise_scheduler = 1;
|
||||
advices++;
|
||||
}
|
||||
|
||||
/* AOF and I/O. */
|
||||
if (!strcasecmp(event,"aof-write-pending-fsync")) {
|
||||
advise_local_disk = 1;
|
||||
advise_disk_contention = 1;
|
||||
advise_ssd = 1;
|
||||
advise_data_writeback = 1;
|
||||
advices += 4;
|
||||
}
|
||||
|
||||
if (!strcasecmp(event,"aof-write-active-child")) {
|
||||
advise_no_appendfsync = 1;
|
||||
advise_data_writeback = 1;
|
||||
advise_ssd = 1;
|
||||
advices += 3;
|
||||
}
|
||||
|
||||
if (!strcasecmp(event,"aof-write-alone")) {
|
||||
advise_local_disk = 1;
|
||||
advise_data_writeback = 1;
|
||||
advise_ssd = 1;
|
||||
advices += 3;
|
||||
}
|
||||
|
||||
if (!strcasecmp(event,"aof-fsync-always")) {
|
||||
advise_relax_fsync_policy = 1;
|
||||
advices++;
|
||||
}
|
||||
|
||||
if (!strcasecmp(event,"aof-fstat") ||
|
||||
!strcasecmp(event,"rdb-unlik-temp-file")) {
|
||||
advise_disk_contention = 1;
|
||||
advise_local_disk = 1;
|
||||
advices += 2;
|
||||
}
|
||||
|
||||
if (!strcasecmp(event,"aof-rewrite-diff-write") ||
|
||||
!strcasecmp(event,"aof-rename")) {
|
||||
advise_write_load_info = 1;
|
||||
advise_data_writeback = 1;
|
||||
advise_ssd = 1;
|
||||
advise_local_disk = 1;
|
||||
advices += 4;
|
||||
}
|
||||
|
||||
/* Expire cycle. */
|
||||
if (!strcasecmp(event,"expire-cycle")) {
|
||||
advise_hz = 1;
|
||||
advise_large_objects = 1;
|
||||
advices += 2;
|
||||
}
|
||||
|
||||
/* Eviction cycle. */
|
||||
if (!strcasecmp(event,"eviction-cycle")) {
|
||||
advise_large_objects = 1;
|
||||
advices++;
|
||||
}
|
||||
|
||||
report = sdscatlen(report,"\n",1);
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
|
||||
if (eventnum == 0) {
|
||||
report = sdscat(report,"Dave, no latency spike was observed during the lifetime of this Redis instance, not in the slightest bit. I honestly think you ought to sit down calmly, take a stress pill, and think things over.\n");
|
||||
} else if (advices == 0) {
|
||||
report = sdscat(report,"\nWhile there are latency events logged, I'm not able to suggest any easy fix. Please use the Redis community to get some help, providing this report in your help request.\n");
|
||||
} else {
|
||||
/* Add all the suggestions accumulated so far. */
|
||||
|
||||
/* Better VM. */
|
||||
report = sdscat(report,"\nI have a few advices for you:\n\n");
|
||||
if (advise_better_vm) {
|
||||
report = sdscat(report,"- If you are using a virtual machine, consider upgrading it with a faster one using an hypervisior that provides less latency during fork() calls. Xen is known to have poor fork() performance. Even in the context of the same VM provider, certain kinds of instances can execute fork faster than others.\n");
|
||||
}
|
||||
|
||||
/* Slow log. */
|
||||
if (advise_slowlog_enabled) {
|
||||
report = sdscatprintf(report,"- There are latency issues with potentially slow commands you are using. Try to enable the Slow Log Redis feature using the command 'CONFIG SET slowlog-log-slower-than %llu'. If the Slow log is disabled Redis is not able to log slow commands execution for you.\n", (unsigned long long)server.latency_monitor_threshold*1000);
|
||||
}
|
||||
|
||||
if (advise_slowlog_tuning) {
|
||||
report = sdscatprintf(report,"- Your current Slow Log configuration only logs events that are slower than your configured latency monitor threshold. Please use 'CONFIG SET slowlog-log-slower-than %llu'.\n", (unsigned long long)server.latency_monitor_threshold*1000);
|
||||
}
|
||||
|
||||
if (advise_slowlog_inspect) {
|
||||
report = sdscat(report,"- Check your Slow Log to understand what are the commands you are running which are too slow to execute. Please check http://redis.io/commands/slowlog for more information.\n");
|
||||
}
|
||||
|
||||
/* Intrinsic latency. */
|
||||
if (advise_scheduler) {
|
||||
report = sdscat(report,"- The system is slow to execute Redis code paths not containing system calls. This usually means the system does not provide Redis CPU time to run for long periods. You should try to:\n"
|
||||
" 1) Lower the system load.\n"
|
||||
" 2) Use a computer / VM just for Redis if you are running other softawre in the same system.\n"
|
||||
" 3) Check if you have a \"noisy neighbour\" problem.\n"
|
||||
" 4) Check with 'redis-cli --intrinsic-latency 100' what is the intrinsic latency in your system.\n"
|
||||
" 5) Check if the problem is allocator-related by recompiling Redis with MALLOC=libc, if you are using Jemalloc. However this may create fragmentation problems.\n");
|
||||
}
|
||||
|
||||
/* AOF / Disk latency. */
|
||||
if (advise_local_disk) {
|
||||
report = sdscat(report,"- It is strongly advised to use local disks for persistence, especially if you are using AOF. Remote disks provided by platform-as-a-service providers are known to be slow.\n");
|
||||
}
|
||||
|
||||
if (advise_ssd) {
|
||||
report = sdscat(report,"- SSD disks are able to reduce fsync latency, and total time needed for snapshotting and AOF log rewriting (resulting in smaller memory usage and smaller final AOF rewrite buffer flushes). With extremely high write load SSD disks can be a good option. However Redis should perform reasonably with high load using normal disks. Use this advice as a last resort.\n");
|
||||
}
|
||||
|
||||
if (advise_data_writeback) {
|
||||
report = sdscat(report,"- Mounting ext3/4 filesystems with data=writeback can provide a performance boost compared to data=ordered, however this mode of operation provides less guarantees, and sometimes it can happen that after a hard crash the AOF file will have an half-written command at the end and will require to be repaired before Redis restarts.\n");
|
||||
}
|
||||
|
||||
if (advise_disk_contention) {
|
||||
report = sdscat(report,"- Try to lower the disk contention. This is often caused by other disk intensive processes running in the same computer (including other Redis instances).\n");
|
||||
}
|
||||
|
||||
if (advise_no_appendfsync) {
|
||||
report = sdscat(report,"- Assuming from the point of view of data safety this is viable in your environment, you could try to enable the 'no-appendfsync-on-rewrite' option, so that fsync will not be performed while there is a child rewriting the AOF file or producing an RDB file (the moment where there is high disk contention).\n");
|
||||
}
|
||||
|
||||
if (advise_relax_fsync_policy && server.aof_fsync == AOF_FSYNC_ALWAYS) {
|
||||
report = sdscat(report,"- Your fsync policy is set to 'always'. It is very hard to get good performances with such a setup, if possible try to relax the fsync policy to 'onesec'.\n");
|
||||
}
|
||||
|
||||
if (advise_write_load_info) {
|
||||
report = sdscat(report,"- Latency during the AOF atomic rename operation or when the final difference is flushed to the AOF file at the end of the rewrite, sometimes is caused by very high write load, causing the AOF buffer to get very large. If possible try to send less commands to accomplish the same work, or use Lua scripts to group multiple operations into a single EVALSHA call.\n");
|
||||
}
|
||||
|
||||
if (advise_hz && server.hz < 100) {
|
||||
report = sdscat(report,"- In order to make the Redis keys expiring process more incremental, try to set the 'hz' configuration parameter to 100 using 'CONFIG SET hz 100'.\n");
|
||||
}
|
||||
|
||||
if (advise_large_objects) {
|
||||
report = sdscat(report,"- Deleting, expiring or evicting (because of maxmemory policy) large objects is a blocking operation. If you have very large objects that are often deleted, expired, or evicted, try to fragment those objects into multiple smaller objects.\n");
|
||||
}
|
||||
}
|
||||
|
||||
return report;
|
||||
}
|
||||
|
||||
/* ---------------------- Latency command implementation -------------------- */
|
||||
|
||||
/* latencyCommand() helper to produce a time-delay reply for all the samples
|
||||
* in memory for the specified time series. */
|
||||
void latencyCommandReplyWithSamples(redisClient *c, struct latencyTimeSeries *ts) {
|
||||
void *replylen = addDeferredMultiBulkLength(c);
|
||||
int samples = 0, j;
|
||||
|
||||
for (j = 0; j < LATENCY_TS_LEN; j++) {
|
||||
int i = (ts->idx + j) % LATENCY_TS_LEN;
|
||||
|
||||
if (ts->samples[i].time == 0) continue;
|
||||
addReplyMultiBulkLen(c,2);
|
||||
addReplyLongLong(c,ts->samples[i].time);
|
||||
addReplyLongLong(c,ts->samples[i].latency);
|
||||
samples++;
|
||||
}
|
||||
setDeferredMultiBulkLength(c,replylen,samples);
|
||||
}
|
||||
|
||||
/* latencyCommand() helper to produce the reply for the LATEST subcommand,
|
||||
* listing the last latency sample for every event type registered so far. */
|
||||
void latencyCommandReplyWithLatestEvents(redisClient *c) {
|
||||
dictIterator *di;
|
||||
dictEntry *de;
|
||||
|
||||
addReplyMultiBulkLen(c,dictSize(server.latency_events));
|
||||
di = dictGetIterator(server.latency_events);
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
char *event = dictGetKey(de);
|
||||
struct latencyTimeSeries *ts = dictGetVal(de);
|
||||
int last = (ts->idx + LATENCY_TS_LEN - 1) % LATENCY_TS_LEN;
|
||||
|
||||
addReplyMultiBulkLen(c,4);
|
||||
addReplyBulkCString(c,event);
|
||||
addReplyLongLong(c,ts->samples[last].time);
|
||||
addReplyLongLong(c,ts->samples[last].latency);
|
||||
addReplyLongLong(c,ts->max);
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
}
|
||||
|
||||
#define LATENCY_GRAPH_COLS 80
|
||||
sds latencyCommandGenSparkeline(char *event, struct latencyTimeSeries *ts) {
|
||||
int j;
|
||||
struct sequence *seq = createSparklineSequence();
|
||||
sds graph = sdsempty();
|
||||
uint32_t min = 0, max = 0;
|
||||
|
||||
for (j = 0; j < LATENCY_TS_LEN; j++) {
|
||||
int i = (ts->idx + j) % LATENCY_TS_LEN;
|
||||
int elapsed;
|
||||
char *label;
|
||||
char buf[64];
|
||||
|
||||
if (ts->samples[i].time == 0) continue;
|
||||
/* Update min and max. */
|
||||
if (seq->length == 0) {
|
||||
min = max = ts->samples[i].latency;
|
||||
} else {
|
||||
if (ts->samples[i].latency > max) max = ts->samples[i].latency;
|
||||
if (ts->samples[i].latency < min) min = ts->samples[i].latency;
|
||||
}
|
||||
/* Use as label the number of seconds / minutes / hours / days
|
||||
* ago the event happened. */
|
||||
elapsed = time(NULL) - ts->samples[i].time;
|
||||
if (elapsed < 60)
|
||||
snprintf(buf,sizeof(buf),"%ds",elapsed);
|
||||
else if (elapsed < 3600)
|
||||
snprintf(buf,sizeof(buf),"%dm",elapsed/60);
|
||||
else if (elapsed < 3600*24)
|
||||
snprintf(buf,sizeof(buf),"%dh",elapsed/3600);
|
||||
else
|
||||
snprintf(buf,sizeof(buf),"%dd",elapsed/(3600*24));
|
||||
label = zstrdup(buf);
|
||||
sparklineSequenceAddSample(seq,ts->samples[i].latency,label);
|
||||
}
|
||||
|
||||
graph = sdscatprintf(graph,
|
||||
"%s - high %lu ms, low %lu ms (all time high %lu ms)\n", event,
|
||||
(unsigned long) max, (unsigned long) min, (unsigned long) ts->max);
|
||||
for (j = 0; j < LATENCY_GRAPH_COLS; j++)
|
||||
graph = sdscatlen(graph,"-",1);
|
||||
graph = sdscatlen(graph,"\n",1);
|
||||
graph = sparklineRender(graph,seq,LATENCY_GRAPH_COLS,4,SPARKLINE_FILL);
|
||||
freeSparklineSequence(seq);
|
||||
return graph;
|
||||
}
|
||||
|
||||
/* LATENCY command implementations.
|
||||
*
|
||||
* LATENCY SAMPLES: return time-latency samples for the specified event.
|
||||
* LATENCY LATEST: return the latest latency for all the events classes.
|
||||
* LATENCY DOCTOR: returns an human readable analysis of instance latency.
|
||||
* LATENCY GRAPH: provide an ASCII graph of the latency of the specified event.
|
||||
*/
|
||||
void latencyCommand(redisClient *c) {
|
||||
struct latencyTimeSeries *ts;
|
||||
|
||||
if (!strcasecmp(c->argv[1]->ptr,"history") && c->argc == 3) {
|
||||
/* LATENCY HISTORY <event> */
|
||||
ts = dictFetchValue(server.latency_events,c->argv[2]->ptr);
|
||||
if (ts == NULL) {
|
||||
addReplyMultiBulkLen(c,0);
|
||||
} else {
|
||||
latencyCommandReplyWithSamples(c,ts);
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"graph") && c->argc == 3) {
|
||||
/* LATENCY GRAPH <event> */
|
||||
sds graph;
|
||||
dictEntry *de;
|
||||
char *event;
|
||||
|
||||
de = dictFind(server.latency_events,c->argv[2]->ptr);
|
||||
if (de == NULL) goto nodataerr;
|
||||
ts = dictGetVal(de);
|
||||
event = dictGetKey(de);
|
||||
|
||||
graph = latencyCommandGenSparkeline(event,ts);
|
||||
addReplyBulkCString(c,graph);
|
||||
sdsfree(graph);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"latest") && c->argc == 2) {
|
||||
/* LATENCY LATEST */
|
||||
latencyCommandReplyWithLatestEvents(c);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"doctor") && c->argc == 2) {
|
||||
/* LATENCY DOCTOR */
|
||||
sds report = createLatencyReport();
|
||||
|
||||
addReplyBulkCBuffer(c,report,sdslen(report));
|
||||
sdsfree(report);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"reset") && c->argc >= 2) {
|
||||
/* LATENCY RESET */
|
||||
if (c->argc == 2) {
|
||||
addReplyLongLong(c,latencyResetEvent(NULL));
|
||||
} else {
|
||||
int j, resets = 0;
|
||||
|
||||
for (j = 2; j < c->argc; j++)
|
||||
resets += latencyResetEvent(c->argv[j]->ptr);
|
||||
addReplyLongLong(c,resets);
|
||||
}
|
||||
} else {
|
||||
addReply(c,shared.syntaxerr);
|
||||
}
|
||||
return;
|
||||
|
||||
nodataerr:
|
||||
/* Common error when the user asks for an event we have no latency
|
||||
* information about. */
|
||||
addReplyErrorFormat(c,
|
||||
"No samples available for event '%s'", (char*) c->argv[2]->ptr);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
/* latency.h -- latency monitor API header file
|
||||
* See latency.c for more information.
|
||||
*
|
||||
* ----------------------------------------------------------------------------
|
||||
*
|
||||
* Copyright (c) 2014, 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 __LATENCY_H
|
||||
#define __LATENCY_H
|
||||
|
||||
#define LATENCY_TS_LEN 160 /* History length for every monitored event. */
|
||||
|
||||
/* Representation of a latency sample: the sampling time and the latency
|
||||
* observed in milliseconds. */
|
||||
struct latencySample {
|
||||
int32_t time; /* We don't use time_t to force 4 bytes usage everywhere. */
|
||||
uint32_t latency; /* Latency in milliseconds. */
|
||||
};
|
||||
|
||||
/* The latency time series for a given event. */
|
||||
struct latencyTimeSeries {
|
||||
int idx; /* Index of the next sample to store. */
|
||||
uint32_t max; /* Max latency observed for this event. */
|
||||
struct latencySample samples[LATENCY_TS_LEN]; /* Latest history. */
|
||||
};
|
||||
|
||||
/* Latency statistics structure. */
|
||||
struct latencyStats {
|
||||
uint32_t all_time_high; /* Absolute max observed since latest reset. */
|
||||
uint32_t avg; /* Average of current samples. */
|
||||
uint32_t min; /* Min of current samples. */
|
||||
uint32_t max; /* Max of current samples. */
|
||||
uint32_t mad; /* Mean absolute deviation. */
|
||||
uint32_t samples; /* Number of non-zero samples. */
|
||||
time_t period; /* Number of seconds since first event and now. */
|
||||
};
|
||||
|
||||
void latencyMonitorInit(void);
|
||||
void latencyAddSample(char *event, mstime_t latency);
|
||||
|
||||
/* Latency monitoring macros. */
|
||||
|
||||
/* Start monitoring an event. We just set the current time. */
|
||||
#define latencyStartMonitor(var) if (server.latency_monitor_threshold) { \
|
||||
var = mstime(); \
|
||||
} else { \
|
||||
var = 0; \
|
||||
}
|
||||
|
||||
/* End monitoring an event, compute the difference with the current time
|
||||
* to check the amount of time elapsed. */
|
||||
#define latencyEndMonitor(var) if (server.latency_monitor_threshold) { \
|
||||
var = mstime() - var; \
|
||||
}
|
||||
|
||||
/* Add the sample only if the elapsed time is >= to the configured threshold. */
|
||||
#define latencyAddSampleIfNeeded(event,var) \
|
||||
if (server.latency_monitor_threshold && \
|
||||
(var) >= server.latency_monitor_threshold) \
|
||||
latencyAddSample((event),(var));
|
||||
|
||||
#endif /* __LATENCY_H */
|
||||
@@ -1,16 +1,16 @@
|
||||
/*
|
||||
* Copyright (c) 2000-2008 Marc Alexander Lehmann <schmorp@schmorp.de>
|
||||
*
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modifica-
|
||||
* tion, are permitted provided that the following conditions are met:
|
||||
*
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
*
|
||||
* 2. 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.
|
||||
*
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MER-
|
||||
* CHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
||||
@@ -73,7 +73,7 @@
|
||||
* and lzf_c.c.
|
||||
*
|
||||
*/
|
||||
unsigned int
|
||||
unsigned int
|
||||
lzf_compress (const void *const in_data, unsigned int in_len,
|
||||
void *out_data, unsigned int out_len);
|
||||
|
||||
@@ -92,7 +92,7 @@ lzf_compress (const void *const in_data, unsigned int in_len,
|
||||
*
|
||||
* This function is very fast, about as fast as a copying loop.
|
||||
*/
|
||||
unsigned int
|
||||
unsigned int
|
||||
lzf_decompress (const void *const in_data, unsigned int in_len,
|
||||
void *out_data, unsigned int out_len);
|
||||
|
||||
|
||||
+4
-4
@@ -1,16 +1,16 @@
|
||||
/*
|
||||
* Copyright (c) 2000-2007 Marc Alexander Lehmann <schmorp@schmorp.de>
|
||||
*
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modifica-
|
||||
* tion, are permitted provided that the following conditions are met:
|
||||
*
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
*
|
||||
* 2. 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.
|
||||
*
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MER-
|
||||
* CHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
||||
|
||||
+4
-4
@@ -1,16 +1,16 @@
|
||||
/*
|
||||
* Copyright (c) 2000-2008 Marc Alexander Lehmann <schmorp@schmorp.de>
|
||||
*
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modifica-
|
||||
* tion, are permitted provided that the following conditions are met:
|
||||
*
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
*
|
||||
* 2. 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.
|
||||
*
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MER-
|
||||
* CHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
||||
|
||||
+5
-5
@@ -1,16 +1,16 @@
|
||||
/*
|
||||
* Copyright (c) 2000-2007 Marc Alexander Lehmann <schmorp@schmorp.de>
|
||||
*
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modifica-
|
||||
* tion, are permitted provided that the following conditions are met:
|
||||
*
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
*
|
||||
* 2. 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.
|
||||
*
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
|
||||
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MER-
|
||||
* CHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
|
||||
@@ -52,7 +52,7 @@
|
||||
#endif
|
||||
*/
|
||||
|
||||
unsigned int
|
||||
unsigned int
|
||||
lzf_decompress (const void *const in_data, unsigned int in_len,
|
||||
void *out_data, unsigned int out_len)
|
||||
{
|
||||
|
||||
@@ -237,6 +237,7 @@ void memtest_test(size_t megabytes, int passes) {
|
||||
memtest_progress_end();
|
||||
memtest_compare_times(m,bytes,pass,4);
|
||||
}
|
||||
free(m);
|
||||
}
|
||||
|
||||
void memtest_non_destructive_invert(void *addr, size_t size) {
|
||||
|
||||
+1
-1
@@ -149,7 +149,7 @@ void migrateCommand(redisClient *c) {
|
||||
addReplySds(c,sdsnew("+NOKEY\r\n"));
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
/* Connect */
|
||||
fd = anetTcpNonBlockConnect(server.neterr,c->argv[1]->ptr,
|
||||
atoi(c->argv[2]->ptr));
|
||||
|
||||
+2
-2
@@ -72,7 +72,7 @@ void queueMultiCommand(redisClient *c) {
|
||||
void discardTransaction(redisClient *c) {
|
||||
freeClientMultiState(c);
|
||||
initClientMultiState(c);
|
||||
c->flags &= ~(REDIS_MULTI|REDIS_DIRTY_CAS|REDIS_DIRTY_EXEC);;
|
||||
c->flags &= ~(REDIS_MULTI|REDIS_DIRTY_CAS|REDIS_DIRTY_EXEC);
|
||||
unwatchAllKeys(c);
|
||||
}
|
||||
|
||||
@@ -214,7 +214,7 @@ void watchForKey(redisClient *c, robj *key) {
|
||||
}
|
||||
/* This key is not already watched in this DB. Let's add it */
|
||||
clients = dictFetchValue(c->db->watched_keys,key);
|
||||
if (!clients) {
|
||||
if (!clients) {
|
||||
clients = listCreate();
|
||||
dictAdd(c->db->watched_keys,key,clients);
|
||||
incrRefCount(key);
|
||||
|
||||
+8
-5
@@ -1028,7 +1028,7 @@ int processMultibulkBuffer(redisClient *c) {
|
||||
qblen = sdslen(c->querybuf);
|
||||
/* Hint the sds library about the amount of bytes this string is
|
||||
* going to contain. */
|
||||
if (qblen < ll+2)
|
||||
if (qblen < (size_t)ll+2)
|
||||
c->querybuf = sdsMakeRoomFor(c->querybuf,ll+2-qblen);
|
||||
}
|
||||
c->bulklen = ll;
|
||||
@@ -1335,6 +1335,7 @@ void clientCommand(redisClient *c) {
|
||||
if (c->argc == 3) {
|
||||
/* Old style syntax: CLIENT KILL <addr> */
|
||||
addr = c->argv[2]->ptr;
|
||||
skipme = 0; /* With the old form, you can kill yourself. */
|
||||
} else if (c->argc > 3) {
|
||||
int i = 2; /* Next option index. */
|
||||
|
||||
@@ -1382,7 +1383,9 @@ void clientCommand(redisClient *c) {
|
||||
while ((ln = listNext(&li)) != NULL) {
|
||||
client = listNodeValue(ln);
|
||||
if (addr && strcmp(getClientPeerId(client),addr) != 0) continue;
|
||||
if (type != -1 && getClientType(client) != type) continue;
|
||||
if (type != -1 &&
|
||||
(client->flags & REDIS_MASTER ||
|
||||
getClientType(client) != type)) continue;
|
||||
if (id != 0 && client->id != id) continue;
|
||||
if (c == client && skipme) continue;
|
||||
|
||||
@@ -1458,7 +1461,7 @@ void rewriteClientCommandVector(redisClient *c, int argc, ...) {
|
||||
va_start(ap,argc);
|
||||
for (j = 0; j < argc; j++) {
|
||||
robj *a;
|
||||
|
||||
|
||||
a = va_arg(ap, robj*);
|
||||
argv[j] = a;
|
||||
incrRefCount(a);
|
||||
@@ -1480,7 +1483,7 @@ void rewriteClientCommandVector(redisClient *c, int argc, ...) {
|
||||
* The new val ref count is incremented, and the old decremented. */
|
||||
void rewriteClientCommandArgument(redisClient *c, int i, robj *newval) {
|
||||
robj *oldval;
|
||||
|
||||
|
||||
redisAssertWithInfo(c,NULL,i < c->argc);
|
||||
oldval = c->argv[i];
|
||||
c->argv[i] = newval;
|
||||
@@ -1524,7 +1527,7 @@ unsigned long getClientOutputBufferMemoryUsage(redisClient *c) {
|
||||
int getClientType(redisClient *c) {
|
||||
if ((c->flags & REDIS_SLAVE) && !(c->flags & REDIS_MONITOR))
|
||||
return REDIS_CLIENT_TYPE_SLAVE;
|
||||
if (dictSize(c->pubsub_channels) || listLength(c->pubsub_patterns))
|
||||
if (c->flags & REDIS_PUBSUB)
|
||||
return REDIS_CLIENT_TYPE_PUBSUB;
|
||||
return REDIS_CLIENT_TYPE_NORMAL;
|
||||
}
|
||||
|
||||
+6
-2
@@ -325,7 +325,11 @@ robj *tryObjectEncoding(robj *o) {
|
||||
* Note that we also avoid using shared integers when maxmemory is used
|
||||
* because every object needs to have a private LRU field for the LRU
|
||||
* algorithm to work well. */
|
||||
if (server.maxmemory == 0 && value >= 0 && value < REDIS_SHARED_INTEGERS) {
|
||||
if ((server.maxmemory == 0 ||
|
||||
(server.maxmemory_policy != REDIS_MAXMEMORY_VOLATILE_LRU &&
|
||||
server.maxmemory_policy != REDIS_MAXMEMORY_ALLKEYS_LRU)) &&
|
||||
value >= 0 && value < REDIS_SHARED_INTEGERS)
|
||||
{
|
||||
decrRefCount(o);
|
||||
incrRefCount(shared.integers[value]);
|
||||
return shared.integers[value];
|
||||
@@ -608,7 +612,7 @@ robj *objectCommandLookupOrReply(redisClient *c, robj *key, robj *reply) {
|
||||
}
|
||||
|
||||
/* Object command allows to inspect the internals of an Redis Object.
|
||||
* Usage: OBJECT <verb> ... arguments ... */
|
||||
* Usage: OBJECT <refcount|encoding|idletime> <key> */
|
||||
void objectCommand(redisClient *c) {
|
||||
robj *o;
|
||||
|
||||
|
||||
+3
-3
@@ -69,7 +69,7 @@ static inline void
|
||||
swapfunc(char *a, char *b, size_t n, int swaptype)
|
||||
{
|
||||
|
||||
if (swaptype <= 1)
|
||||
if (swaptype <= 1)
|
||||
swapcode(long, a, b, n)
|
||||
else
|
||||
swapcode(char, a, b, n)
|
||||
@@ -160,9 +160,9 @@ loop: SWAPINIT(a, es);
|
||||
(lrange > _r && rrange > _r)))
|
||||
_pqsort(a, r / es, es, cmp, lrange, rrange);
|
||||
}
|
||||
if ((r = pd - pc) > es) {
|
||||
if ((r = pd - pc) > es) {
|
||||
void *_l, *_r;
|
||||
|
||||
|
||||
/* Iterate rather than recurse to save stack space */
|
||||
a = pn - r;
|
||||
n = r / es;
|
||||
|
||||
+14
-4
@@ -47,6 +47,12 @@ int listMatchPubsubPattern(void *a, void *b) {
|
||||
(equalStringObjects(pa->pattern,pb->pattern));
|
||||
}
|
||||
|
||||
/* Return the number of channels + patterns a client is subscribed to. */
|
||||
int clientSubscriptionsCount(redisClient *c) {
|
||||
return dictSize(c->pubsub_channels)+
|
||||
listLength(c->pubsub_patterns);
|
||||
}
|
||||
|
||||
/* Subscribe a client to a channel. Returns 1 if the operation succeeded, or
|
||||
* 0 if the client was already subscribed to that channel. */
|
||||
int pubsubSubscribeChannel(redisClient *c, robj *channel) {
|
||||
@@ -73,7 +79,7 @@ int pubsubSubscribeChannel(redisClient *c, robj *channel) {
|
||||
addReply(c,shared.mbulkhdr[3]);
|
||||
addReply(c,shared.subscribebulk);
|
||||
addReplyBulk(c,channel);
|
||||
addReplyLongLong(c,dictSize(c->pubsub_channels)+listLength(c->pubsub_patterns));
|
||||
addReplyLongLong(c,clientSubscriptionsCount(c));
|
||||
return retval;
|
||||
}
|
||||
|
||||
@@ -135,7 +141,7 @@ int pubsubSubscribePattern(redisClient *c, robj *pattern) {
|
||||
addReply(c,shared.mbulkhdr[3]);
|
||||
addReply(c,shared.psubscribebulk);
|
||||
addReplyBulk(c,pattern);
|
||||
addReplyLongLong(c,dictSize(c->pubsub_channels)+listLength(c->pubsub_patterns));
|
||||
addReplyLongLong(c,clientSubscriptionsCount(c));
|
||||
return retval;
|
||||
}
|
||||
|
||||
@@ -168,7 +174,7 @@ int pubsubUnsubscribePattern(redisClient *c, robj *pattern, int notify) {
|
||||
}
|
||||
|
||||
/* Unsubscribe from all the channels. Return the number of channels the
|
||||
* client was subscribed from. */
|
||||
* client was subscribed to. */
|
||||
int pubsubUnsubscribeAllChannels(redisClient *c, int notify) {
|
||||
dictIterator *di = dictGetSafeIterator(c->pubsub_channels);
|
||||
dictEntry *de;
|
||||
@@ -273,6 +279,7 @@ void subscribeCommand(redisClient *c) {
|
||||
|
||||
for (j = 1; j < c->argc; j++)
|
||||
pubsubSubscribeChannel(c,c->argv[j]);
|
||||
c->flags |= REDIS_PUBSUB;
|
||||
}
|
||||
|
||||
void unsubscribeCommand(redisClient *c) {
|
||||
@@ -284,6 +291,7 @@ void unsubscribeCommand(redisClient *c) {
|
||||
for (j = 1; j < c->argc; j++)
|
||||
pubsubUnsubscribeChannel(c,c->argv[j],1);
|
||||
}
|
||||
if (clientSubscriptionsCount(c) == 0) c->flags &= ~REDIS_PUBSUB;
|
||||
}
|
||||
|
||||
void psubscribeCommand(redisClient *c) {
|
||||
@@ -291,6 +299,7 @@ void psubscribeCommand(redisClient *c) {
|
||||
|
||||
for (j = 1; j < c->argc; j++)
|
||||
pubsubSubscribePattern(c,c->argv[j]);
|
||||
c->flags |= REDIS_PUBSUB;
|
||||
}
|
||||
|
||||
void punsubscribeCommand(redisClient *c) {
|
||||
@@ -302,6 +311,7 @@ void punsubscribeCommand(redisClient *c) {
|
||||
for (j = 1; j < c->argc; j++)
|
||||
pubsubUnsubscribePattern(c,c->argv[j],1);
|
||||
}
|
||||
if (clientSubscriptionsCount(c) == 0) c->flags &= ~REDIS_PUBSUB;
|
||||
}
|
||||
|
||||
void publishCommand(redisClient *c) {
|
||||
@@ -345,7 +355,7 @@ void pubsubCommand(redisClient *c) {
|
||||
list *l = dictFetchValue(server.pubsub_channels,c->argv[j]);
|
||||
|
||||
addReplyBulk(c,c->argv[j]);
|
||||
addReplyBulkLongLong(c,l ? listLength(l) : 0);
|
||||
addReplyLongLong(c,l ? listLength(l) : 0);
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[1]->ptr,"numpat") && c->argc == 2) {
|
||||
/* PUBSUB NUMPAT */
|
||||
|
||||
+2
-2
@@ -66,7 +66,7 @@
|
||||
#define HI_BIT (1L << (2 * N - 1))
|
||||
|
||||
static uint32_t x[3] = { X0, X1, X2 }, a[3] = { A0, A1, A2 }, c = C;
|
||||
static void next();
|
||||
static void next(void);
|
||||
|
||||
int32_t redisLrand48() {
|
||||
next();
|
||||
@@ -77,7 +77,7 @@ void redisSrand48(int32_t seedval) {
|
||||
SEED(X0, LOW(seedval), HIGH(seedval));
|
||||
}
|
||||
|
||||
static void next() {
|
||||
static void next(void) {
|
||||
uint32_t p[2], q[2], r[2], carry0, carry1;
|
||||
|
||||
MUL(a[0], x[0], p);
|
||||
|
||||
@@ -671,7 +671,7 @@ int rdbSave(char *filename) {
|
||||
sds keystr = dictGetKey(de);
|
||||
robj key, *o = dictGetVal(de);
|
||||
long long expire;
|
||||
|
||||
|
||||
initStaticStringObject(key,keystr);
|
||||
expire = getExpire(db,&key);
|
||||
if (rdbSaveKeyValuePair(&rdb,&key,o,expire,now) == -1) goto werr;
|
||||
@@ -687,7 +687,7 @@ int rdbSave(char *filename) {
|
||||
* loading code skips the check in this case. */
|
||||
cksum = rdb.cksum;
|
||||
memrev64ifbe(&cksum);
|
||||
rioWrite(&rdb,&cksum,8);
|
||||
if (rioWrite(&rdb,&cksum,8) == 0) goto werr;
|
||||
|
||||
/* Make sure data will not remain on the OS's output buffers */
|
||||
if (fflush(fp) == EOF) goto werr;
|
||||
@@ -745,6 +745,8 @@ int rdbSaveBackground(char *filename) {
|
||||
} else {
|
||||
/* Parent */
|
||||
server.stat_fork_time = ustime()-start;
|
||||
server.stat_fork_rate = (double) zmalloc_used_memory() * 1000000 / server.stat_fork_time / (1024*1024*1024); /* GB per second. */
|
||||
latencyAddSampleIfNeeded("fork",server.stat_fork_time/1000);
|
||||
if (childpid == -1) {
|
||||
server.lastbgsave_status = REDIS_ERR;
|
||||
redisLog(REDIS_WARNING,"Can't save in background: fork: %s",
|
||||
@@ -763,7 +765,7 @@ int rdbSaveBackground(char *filename) {
|
||||
void rdbRemoveTempFile(pid_t childpid) {
|
||||
char tmpfile[256];
|
||||
|
||||
snprintf(tmpfile,256,"temp-%d.rdb", (int) childpid);
|
||||
snprintf(tmpfile,sizeof(tmpfile),"temp-%d.rdb", (int) childpid);
|
||||
unlink(tmpfile);
|
||||
}
|
||||
|
||||
@@ -941,7 +943,7 @@ robj *rdbLoadObject(int rdbtype, rio *rdb) {
|
||||
|
||||
/* Add pair to hash table */
|
||||
ret = dictAdd((dict*)o->ptr, field, value);
|
||||
redisAssert(ret == REDIS_OK);
|
||||
redisAssert(ret == DICT_OK);
|
||||
}
|
||||
|
||||
/* All pairs should be read by now */
|
||||
@@ -1201,9 +1203,14 @@ void backgroundSaveDoneHandler(int exitcode, int bysignal) {
|
||||
redisLog(REDIS_WARNING, "Background saving error");
|
||||
server.lastbgsave_status = REDIS_ERR;
|
||||
} else {
|
||||
mstime_t latency;
|
||||
|
||||
redisLog(REDIS_WARNING,
|
||||
"Background saving terminated by signal %d", bysignal);
|
||||
latencyStartMonitor(latency);
|
||||
rdbRemoveTempFile(server.rdb_child_pid);
|
||||
latencyEndMonitor(latency);
|
||||
latencyAddSampleIfNeeded("rdb-unlink-temp-file",latency);
|
||||
/* SIGUSR1 is whitelisted, so we have a way to kill a child without
|
||||
* tirggering an error conditon. */
|
||||
if (bysignal != SIGUSR1)
|
||||
@@ -1214,7 +1221,7 @@ void backgroundSaveDoneHandler(int exitcode, int bysignal) {
|
||||
server.rdb_save_time_start = -1;
|
||||
/* Possibly there are slaves waiting for a BGSAVE in order to be served
|
||||
* (the first stage of SYNC is a bulk transfer of dump.rdb) */
|
||||
updateSlavesWaitingBgsave(exitcode == 0 ? REDIS_OK : REDIS_ERR);
|
||||
updateSlavesWaitingBgsave((!bysignal && exitcode == 0) ? REDIS_OK : REDIS_ERR);
|
||||
}
|
||||
|
||||
void saveCommand(redisClient *c) {
|
||||
|
||||
+21
-11
@@ -77,6 +77,7 @@ static struct config {
|
||||
int dbnum;
|
||||
sds dbnumstr;
|
||||
char *tests;
|
||||
char *auth;
|
||||
} config;
|
||||
|
||||
typedef struct _client {
|
||||
@@ -213,7 +214,7 @@ static void readHandler(aeEventLoop *el, int fd, void *privdata, int mask) {
|
||||
freeReplyObject(reply);
|
||||
|
||||
if (c->selectlen) {
|
||||
int j;
|
||||
size_t j;
|
||||
|
||||
/* This is the OK from SELECT. Just discard the SELECT
|
||||
* from the buffer. */
|
||||
@@ -325,6 +326,13 @@ static client createClient(char *cmd, size_t len, client from) {
|
||||
* the example client buffer. */
|
||||
c->obuf = sdsempty();
|
||||
|
||||
if (config.auth) {
|
||||
char *buf = NULL;
|
||||
int len = redisFormatCommand(&buf, "AUTH %s", config.auth);
|
||||
c->obuf = sdscatlen(c->obuf, buf, len);
|
||||
free(buf);
|
||||
}
|
||||
|
||||
/* If a DB number different than zero is selected, prefix our request
|
||||
* buffer with the SELECT command, that will be discarded the first
|
||||
* time the replies are received, so if the client is reused the
|
||||
@@ -346,6 +354,7 @@ static client createClient(char *cmd, size_t len, client from) {
|
||||
for (j = 0; j < config.pipeline; j++)
|
||||
c->obuf = sdscatlen(c->obuf,cmd,len);
|
||||
}
|
||||
|
||||
c->written = 0;
|
||||
c->pending = config.pipeline;
|
||||
c->randptr = NULL;
|
||||
@@ -359,7 +368,7 @@ static client createClient(char *cmd, size_t len, client from) {
|
||||
c->randfree = 0;
|
||||
c->randptr = zmalloc(sizeof(char*)*c->randlen);
|
||||
/* copy the offsets. */
|
||||
for (j = 0; j < c->randlen; j++) {
|
||||
for (j = 0; j < (int)c->randlen; j++) {
|
||||
c->randptr[j] = c->obuf + (from->randptr[j]-from->obuf);
|
||||
/* Adjust for the different select prefix length. */
|
||||
c->randptr[j] += c->selectlen - from->selectlen;
|
||||
@@ -389,15 +398,6 @@ static client createClient(char *cmd, size_t len, client from) {
|
||||
|
||||
static void createMissingClients(client c) {
|
||||
int n = 0;
|
||||
char *buf = c->obuf;
|
||||
size_t buflen = sdslen(c->obuf);
|
||||
|
||||
/* If we are cloning from a client with a SELECT prefix, skip it since the
|
||||
* client will be created with the prefixed SELECT if needed. */
|
||||
if (c->selectlen) {
|
||||
buf += c->selectlen;
|
||||
buflen -= c->selectlen;
|
||||
}
|
||||
|
||||
while(config.liveclients < config.numclients) {
|
||||
createClient(NULL,0,c);
|
||||
@@ -489,6 +489,9 @@ int parseOptions(int argc, const char **argv) {
|
||||
} else if (!strcmp(argv[i],"-s")) {
|
||||
if (lastarg) goto invalid;
|
||||
config.hostsocket = strdup(argv[++i]);
|
||||
} else if (!strcmp(argv[i],"-a") ) {
|
||||
if (lastarg) goto invalid;
|
||||
config.auth = strdup(argv[++i]);
|
||||
} else if (!strcmp(argv[i],"-d")) {
|
||||
if (lastarg) goto invalid;
|
||||
config.datasize = atoi(argv[++i]);
|
||||
@@ -550,6 +553,7 @@ usage:
|
||||
" -h <hostname> Server hostname (default 127.0.0.1)\n"
|
||||
" -p <port> Server port (default 6379)\n"
|
||||
" -s <socket> Server socket (overrides host and port)\n"
|
||||
" -a <password> Password for Redis Auth\n"
|
||||
" -c <clients> Number of parallel connections (default 50)\n"
|
||||
" -n <requests> Total number of requests (default 10000)\n"
|
||||
" -d <size> Data size of SET/GET value in bytes (default 2)\n"
|
||||
@@ -592,6 +596,11 @@ int showThroughput(struct aeEventLoop *eventLoop, long long id, void *clientData
|
||||
REDIS_NOTUSED(id);
|
||||
REDIS_NOTUSED(clientData);
|
||||
|
||||
if (config.liveclients == 0) {
|
||||
fprintf(stderr,"All clients disconnected... aborting.\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (config.csv) return 250;
|
||||
float dt = (float)(mstime()-config.start)/1000.0;
|
||||
float rps = (float)config.requests_finished/dt;
|
||||
@@ -646,6 +655,7 @@ int main(int argc, const char **argv) {
|
||||
config.hostsocket = NULL;
|
||||
config.tests = NULL;
|
||||
config.dbnum = 0;
|
||||
config.auth = NULL;
|
||||
|
||||
i = parseOptions(argc,argv);
|
||||
argc -= i;
|
||||
|
||||
@@ -138,12 +138,14 @@ typedef struct {
|
||||
* at runtime to avoid strange compiler optimizations. */
|
||||
static double R_Zero, R_PosInf, R_NegInf, R_Nan;
|
||||
|
||||
#define MAX_TYPES_NUM 256
|
||||
#define MAX_TYPE_NAME_LEN 16
|
||||
/* store string types for output */
|
||||
static char types[256][16];
|
||||
static char types[MAX_TYPES_NUM][MAX_TYPE_NAME_LEN];
|
||||
|
||||
/* Return true if 't' is a valid object type. */
|
||||
int checkType(unsigned char t) {
|
||||
/* In case a new object type is added, update the following
|
||||
/* In case a new object type is added, update the following
|
||||
* condition as necessary. */
|
||||
return
|
||||
(t >= REDIS_HASH_ZIPMAP && t <= REDIS_HASH_ZIPLIST) ||
|
||||
@@ -166,7 +168,7 @@ int readBytes(void *target, long num) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
int processHeader() {
|
||||
int processHeader(void) {
|
||||
char buf[10] = "_________";
|
||||
int dump_version;
|
||||
|
||||
@@ -335,6 +337,7 @@ char* loadStringObject() {
|
||||
if (len == REDIS_RDB_LENERR) return NULL;
|
||||
|
||||
char *buf = malloc(sizeof(char) * (len+1));
|
||||
if (buf == NULL) return NULL;
|
||||
buf[len] = '\0';
|
||||
if (!readBytes(buf, len)) {
|
||||
free(buf);
|
||||
@@ -514,7 +517,7 @@ entry loadEntry() {
|
||||
return e;
|
||||
} else {
|
||||
/* optionally consume expire */
|
||||
if (e.type == REDIS_EXPIRETIME ||
|
||||
if (e.type == REDIS_EXPIRETIME ||
|
||||
e.type == REDIS_EXPIRETIME_MS) {
|
||||
if (!processTime(e.type)) return e;
|
||||
if (!loadType(&e)) return e;
|
||||
@@ -600,7 +603,7 @@ void printErrorStack(entry *e) {
|
||||
}
|
||||
}
|
||||
|
||||
void process() {
|
||||
void process(void) {
|
||||
uint64_t num_errors = 0, num_valid_ops = 0, num_valid_bytes = 0;
|
||||
entry entry;
|
||||
int dump_version = processHeader();
|
||||
@@ -611,7 +614,7 @@ void process() {
|
||||
printf("RDB version >= 5 but no room for checksum.\n");
|
||||
exit(1);
|
||||
}
|
||||
positions[0].size -= 8;;
|
||||
positions[0].size -= 8;
|
||||
}
|
||||
|
||||
level = 1;
|
||||
|
||||
+62
-39
@@ -94,10 +94,11 @@ static struct config {
|
||||
sds mb_delim;
|
||||
char prompt[128];
|
||||
char *eval;
|
||||
int last_cmd_type;
|
||||
} config;
|
||||
|
||||
static volatile sig_atomic_t force_cancel_loop = 0;
|
||||
static void usage();
|
||||
static void usage(void);
|
||||
static void slaveMode(void);
|
||||
char *redisGitSHA1(void);
|
||||
char *redisGitDirty(void);
|
||||
@@ -131,7 +132,7 @@ static void cliRefreshPrompt(void) {
|
||||
strchr(config.hostip,':') ? "[%s]:%d" : "%s:%d",
|
||||
config.hostip, config.hostport);
|
||||
/* Add [dbnum] if needed */
|
||||
if (config.dbnum != 0)
|
||||
if (config.dbnum != 0 && config.last_cmd_type != REDIS_REPLY_ERROR)
|
||||
len += snprintf(config.prompt+len,sizeof(config.prompt)-len,"[%d]",
|
||||
config.dbnum);
|
||||
snprintf(config.prompt+len,sizeof(config.prompt)-len,"> ");
|
||||
@@ -157,7 +158,7 @@ typedef struct {
|
||||
static helpEntry *helpEntries;
|
||||
static int helpEntriesLen;
|
||||
|
||||
static sds cliVersion() {
|
||||
static sds cliVersion(void) {
|
||||
sds version;
|
||||
version = sdscatprintf(sdsempty(), "%s", REDIS_VERSION);
|
||||
|
||||
@@ -171,7 +172,7 @@ static sds cliVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
static void cliInitHelp() {
|
||||
static void cliInitHelp(void) {
|
||||
int commandslen = sizeof(commandHelp)/sizeof(struct commandHelp);
|
||||
int groupslen = sizeof(commandGroups)/sizeof(char*);
|
||||
int i, len, pos = 0;
|
||||
@@ -210,7 +211,7 @@ static void cliOutputCommandHelp(struct commandHelp *help, int group) {
|
||||
}
|
||||
|
||||
/* Print generic help. */
|
||||
static void cliOutputGenericHelp() {
|
||||
static void cliOutputGenericHelp(void) {
|
||||
sds version = cliVersion();
|
||||
printf(
|
||||
"redis-cli %s\r\n"
|
||||
@@ -320,8 +321,10 @@ static int cliSelect() {
|
||||
|
||||
reply = redisCommand(context,"SELECT %d",config.dbnum);
|
||||
if (reply != NULL) {
|
||||
int result = REDIS_OK;
|
||||
if (reply->type == REDIS_REPLY_ERROR) result = REDIS_ERR;
|
||||
freeReplyObject(reply);
|
||||
return REDIS_OK;
|
||||
return result;
|
||||
}
|
||||
return REDIS_ERR;
|
||||
}
|
||||
@@ -365,7 +368,7 @@ static int cliConnect(int force) {
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
static void cliPrintContextError() {
|
||||
static void cliPrintContextError(void) {
|
||||
if (context == NULL) return;
|
||||
fprintf(stderr,"Error: %s\n",context->errstr);
|
||||
}
|
||||
@@ -514,8 +517,11 @@ static int cliReadReply(int output_raw_strings) {
|
||||
int output = 1;
|
||||
|
||||
if (redisGetReply(context,&_reply) != REDIS_OK) {
|
||||
if (config.shutdown)
|
||||
if (config.shutdown) {
|
||||
redisFree(context);
|
||||
context = NULL;
|
||||
return REDIS_OK;
|
||||
}
|
||||
if (config.interactive) {
|
||||
/* Filter cases where we should reconnect */
|
||||
if (context->err == REDIS_ERR_IO && errno == ECONNRESET)
|
||||
@@ -530,6 +536,8 @@ static int cliReadReply(int output_raw_strings) {
|
||||
|
||||
reply = (redisReply*)_reply;
|
||||
|
||||
config.last_cmd_type = reply->type;
|
||||
|
||||
/* Check if we need to connect to a different node and reissue the
|
||||
* request. */
|
||||
if (config.cluster_mode && reply->type == REDIS_REPLY_ERROR &&
|
||||
@@ -598,8 +606,11 @@ static int cliSendCommand(int argc, char **argv, int repeat) {
|
||||
(!strcasecmp(argv[1],"nodes") ||
|
||||
!strcasecmp(argv[1],"info"))) ||
|
||||
(argc == 2 && !strcasecmp(command,"client") &&
|
||||
!strcasecmp(argv[1],"list")))
|
||||
|
||||
!strcasecmp(argv[1],"list")) ||
|
||||
(argc == 3 && !strcasecmp(command,"latency") &&
|
||||
!strcasecmp(argv[1],"graph")) ||
|
||||
(argc == 2 && !strcasecmp(command,"latency") &&
|
||||
!strcasecmp(argv[1],"doctor")))
|
||||
{
|
||||
output_raw = 1;
|
||||
}
|
||||
@@ -635,6 +646,7 @@ static int cliSendCommand(int argc, char **argv, int repeat) {
|
||||
printf("Entering slave output mode... (press Ctrl-C to quit)\n");
|
||||
slaveMode();
|
||||
config.slave_mode = 0;
|
||||
free(argvlen);
|
||||
return REDIS_ERR; /* Error = slaveMode lost connection to master */
|
||||
}
|
||||
|
||||
@@ -646,6 +658,8 @@ static int cliSendCommand(int argc, char **argv, int repeat) {
|
||||
if (!strcasecmp(command,"select") && argc == 2) {
|
||||
config.dbnum = atoi(argv[1]);
|
||||
cliRefreshPrompt();
|
||||
} else if (!strcasecmp(command,"auth") && argc == 2) {
|
||||
cliSelect();
|
||||
}
|
||||
}
|
||||
if (config.interval) usleep(config.interval);
|
||||
@@ -720,6 +734,8 @@ static int parseOptions(int argc, char **argv) {
|
||||
config.auth = argv[++i];
|
||||
} else if (!strcmp(argv[i],"--raw")) {
|
||||
config.output = OUTPUT_RAW;
|
||||
} else if (!strcmp(argv[i],"--no-raw")) {
|
||||
config.output = OUTPUT_STANDARD;
|
||||
} else if (!strcmp(argv[i],"--csv")) {
|
||||
config.output = OUTPUT_CSV;
|
||||
} else if (!strcmp(argv[i],"--latency")) {
|
||||
@@ -791,7 +807,7 @@ static sds readArgFromStdin(void) {
|
||||
return arg;
|
||||
}
|
||||
|
||||
static void usage() {
|
||||
static void usage(void) {
|
||||
sds version = cliVersion();
|
||||
fprintf(stderr,
|
||||
"redis-cli %s\n"
|
||||
@@ -810,6 +826,7 @@ static void usage() {
|
||||
" -c Enable cluster mode (follow -ASK and -MOVED redirections).\n"
|
||||
" --raw Use raw formatting for replies (default when STDOUT is\n"
|
||||
" not a tty).\n"
|
||||
" --no-raw Force formatted output even when STDOUT is not a tty.\n"
|
||||
" --csv Output in CSV format.\n"
|
||||
" --latency Enter a special mode continuously sampling latency.\n"
|
||||
" --latency-history Like --latency but tracking latency changes over time.\n"
|
||||
@@ -858,8 +875,7 @@ static char **convertToSds(int count, char** args) {
|
||||
return sds;
|
||||
}
|
||||
|
||||
#define LINE_BUFLEN 4096
|
||||
static void repl() {
|
||||
static void repl(void) {
|
||||
sds historyfile = NULL;
|
||||
int history = 0;
|
||||
char *line;
|
||||
@@ -1149,7 +1165,7 @@ static void getRDB(void) {
|
||||
|
||||
while(payload) {
|
||||
ssize_t nread, nwritten;
|
||||
|
||||
|
||||
nread = read(s,buf,(payload > sizeof(buf)) ? sizeof(buf) : payload);
|
||||
if (nread <= 0) {
|
||||
fprintf(stderr,"I/O Error reading RDB payload from socket\n");
|
||||
@@ -1232,7 +1248,7 @@ static void pipeMode(void) {
|
||||
errors++;
|
||||
} else if (eof && reply->type == REDIS_REPLY_STRING &&
|
||||
reply->len == 20) {
|
||||
/* Check if this is the reply to our final ECHO
|
||||
/* Check if this is the reply to our final ECHO
|
||||
* command. If so everything was received
|
||||
* from the server. */
|
||||
if (memcmp(reply->str,magic,20) == 0) {
|
||||
@@ -1253,7 +1269,7 @@ static void pipeMode(void) {
|
||||
/* Transfer current buffer to server. */
|
||||
if (obuf_len != 0) {
|
||||
ssize_t nwritten = write(fd,obuf+obuf_pos,obuf_len);
|
||||
|
||||
|
||||
if (nwritten == -1) {
|
||||
if (errno != EAGAIN && errno != EINTR) {
|
||||
fprintf(stderr, "Error writing to the server: %s\n",
|
||||
@@ -1356,7 +1372,7 @@ static redisReply *sendScan(unsigned long long *it) {
|
||||
/* Validate our types are correct */
|
||||
assert(reply->element[0]->type == REDIS_REPLY_STRING);
|
||||
assert(reply->element[1]->type == REDIS_REPLY_ARRAY);
|
||||
|
||||
|
||||
/* Update iterator */
|
||||
*it = atoi(reply->element[0]->str);
|
||||
|
||||
@@ -1368,7 +1384,7 @@ static int getDbSize(void) {
|
||||
int size;
|
||||
|
||||
reply = redisCommand(context, "DBSIZE");
|
||||
|
||||
|
||||
if(reply == NULL || reply->type != REDIS_REPLY_INTEGER) {
|
||||
fprintf(stderr, "Couldn't determine DBSIZE!\n");
|
||||
exit(1);
|
||||
@@ -1402,7 +1418,7 @@ static int toIntType(char *key, char *type) {
|
||||
|
||||
static void getKeyTypes(redisReply *keys, int *types) {
|
||||
redisReply *reply;
|
||||
int i;
|
||||
unsigned int i;
|
||||
|
||||
/* Pipeline TYPE commands */
|
||||
for(i=0;i<keys->elements;i++) {
|
||||
@@ -1421,25 +1437,25 @@ static void getKeyTypes(redisReply *keys, int *types) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
types[i] = toIntType(keys->element[i]->str, reply->str);
|
||||
types[i] = toIntType(keys->element[i]->str, reply->str);
|
||||
freeReplyObject(reply);
|
||||
}
|
||||
}
|
||||
|
||||
static void getKeySizes(redisReply *keys, int *types,
|
||||
unsigned long long *sizes)
|
||||
static void getKeySizes(redisReply *keys, int *types,
|
||||
unsigned long long *sizes)
|
||||
{
|
||||
redisReply *reply;
|
||||
char *sizecmds[] = {"STRLEN","LLEN","SCARD","HLEN","ZCARD"};
|
||||
int i;
|
||||
unsigned int i;
|
||||
|
||||
/* Pipeline size commands */
|
||||
for(i=0;i<keys->elements;i++) {
|
||||
/* Skip keys that were deleted */
|
||||
if(types[i]==TYPE_NONE)
|
||||
if(types[i]==TYPE_NONE)
|
||||
continue;
|
||||
|
||||
redisAppendCommand(context, "%s %s", sizecmds[types[i]],
|
||||
redisAppendCommand(context, "%s %s", sizecmds[types[i]],
|
||||
keys->element[i]->str);
|
||||
}
|
||||
|
||||
@@ -1459,14 +1475,14 @@ static void getKeySizes(redisReply *keys, int *types,
|
||||
} else if(reply->type != REDIS_REPLY_INTEGER) {
|
||||
/* Theoretically the key could have been removed and
|
||||
* added as a different type between TYPE and SIZE */
|
||||
fprintf(stderr,
|
||||
fprintf(stderr,
|
||||
"Warning: %s on '%s' failed (may have changed type)\n",
|
||||
sizecmds[types[i]], keys->element[i]->str);
|
||||
sizes[i] = 0;
|
||||
} else {
|
||||
sizes[i] = reply->integer;
|
||||
}
|
||||
|
||||
|
||||
freeReplyObject(reply);
|
||||
}
|
||||
}
|
||||
@@ -1478,7 +1494,8 @@ static void findBigKeys(void) {
|
||||
char *typename[] = {"string","list","set","hash","zset"};
|
||||
char *typeunit[] = {"bytes","items","members","fields","members"};
|
||||
redisReply *reply, *keys;
|
||||
int type, *types=NULL, arrsize=0, i;
|
||||
unsigned int arrsize=0, i;
|
||||
int type, *types=NULL;
|
||||
double pct;
|
||||
|
||||
/* Total keys pre scanning */
|
||||
@@ -1523,12 +1540,12 @@ static void findBigKeys(void) {
|
||||
/* Retreive types and then sizes */
|
||||
getKeyTypes(keys, types);
|
||||
getKeySizes(keys, types, sizes);
|
||||
|
||||
|
||||
/* Now update our stats */
|
||||
for(i=0;i<keys->elements;i++) {
|
||||
if((type = types[i]) == TYPE_NONE)
|
||||
continue;
|
||||
|
||||
|
||||
totalsize[type] += sizes[i];
|
||||
counts[type]++;
|
||||
totlen += keys->element[i]->len;
|
||||
@@ -1548,7 +1565,7 @@ static void findBigKeys(void) {
|
||||
}
|
||||
|
||||
/* Keep track of the biggest size for this type */
|
||||
biggest[type] = sizes[i];
|
||||
biggest[type] = sizes[i];
|
||||
}
|
||||
|
||||
/* Update overall progress */
|
||||
@@ -1561,7 +1578,7 @@ static void findBigKeys(void) {
|
||||
if(sampled && (sampled %100) == 0 && config.interval) {
|
||||
usleep(config.interval);
|
||||
}
|
||||
|
||||
|
||||
freeReplyObject(reply);
|
||||
} while(it != 0);
|
||||
|
||||
@@ -1662,7 +1679,7 @@ void bytesToHuman(char *s, long long n) {
|
||||
}
|
||||
}
|
||||
|
||||
static void statMode() {
|
||||
static void statMode(void) {
|
||||
redisReply *reply;
|
||||
long aux, requests = 0;
|
||||
int i = 0;
|
||||
@@ -1748,7 +1765,7 @@ static void statMode() {
|
||||
* Scan mode
|
||||
*--------------------------------------------------------------------------- */
|
||||
|
||||
static void scanMode() {
|
||||
static void scanMode(void) {
|
||||
redisReply *reply;
|
||||
unsigned long long cur = 0;
|
||||
|
||||
@@ -1765,7 +1782,7 @@ static void scanMode() {
|
||||
printf("ERROR: %s\n", reply->str);
|
||||
exit(1);
|
||||
} else {
|
||||
int j;
|
||||
unsigned int j;
|
||||
|
||||
cur = strtoull(reply->element[0]->str,NULL,10);
|
||||
for (j = 0; j < reply->element[1]->elements; j++)
|
||||
@@ -1836,11 +1853,15 @@ static void intrinsicLatencyMode(void) {
|
||||
printf("Max latency so far: %lld microseconds.\n", max_latency);
|
||||
}
|
||||
|
||||
double avg_us = (double)run_time/runs;
|
||||
double avg_ns = avg_us * 10e3;
|
||||
if (force_cancel_loop || end > test_end) {
|
||||
printf("\n%lld total runs (avg %lld microseconds per run).\n",
|
||||
runs, run_time/runs);
|
||||
printf("Worst run took %.02fx times the average.\n",
|
||||
(double) max_latency / (run_time/runs));
|
||||
printf("\n%lld total runs "
|
||||
"(avg latency: "
|
||||
"%.4f microseconds / %.2f nanoseconds per run).\n",
|
||||
runs, avg_us, avg_ns);
|
||||
printf("Worst run took %.0fx longer than the average latency.\n",
|
||||
max_latency / avg_us);
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
@@ -1879,6 +1900,8 @@ int main(int argc, char **argv) {
|
||||
config.stdinarg = 0;
|
||||
config.auth = NULL;
|
||||
config.eval = NULL;
|
||||
config.last_cmd_type = -1;
|
||||
|
||||
if (!isatty(fileno(stdout)) && (getenv("FAKETTY") == NULL))
|
||||
config.output = OUTPUT_RAW;
|
||||
else
|
||||
|
||||
+263
-106
@@ -65,7 +65,6 @@ double R_Zero, R_PosInf, R_NegInf, R_Nan;
|
||||
|
||||
/* Global vars */
|
||||
struct redisServer server; /* server global state */
|
||||
struct redisCommand *commandTable;
|
||||
|
||||
/* Our command table.
|
||||
*
|
||||
@@ -112,48 +111,52 @@ struct redisCommand *commandTable;
|
||||
* server this data. Normally no command is accepted in this condition
|
||||
* but just a few.
|
||||
* M: Do not automatically propagate the command on MONITOR.
|
||||
* F: Fast command: O(1) or O(log(N)) command that should never delay
|
||||
* its execution as long as the kernel scheduler is giving us time.
|
||||
* Note that commands that may trigger a DEL as a side effect (like SET)
|
||||
* are not fast commands.
|
||||
*/
|
||||
struct redisCommand redisCommandTable[] = {
|
||||
{"get",getCommand,2,"r",0,NULL,1,1,1,0,0},
|
||||
{"set",setCommand,-3,"wm",0,noPreloadGetKeys,1,1,1,0,0},
|
||||
{"setnx",setnxCommand,3,"wm",0,noPreloadGetKeys,1,1,1,0,0},
|
||||
{"setex",setexCommand,4,"wm",0,noPreloadGetKeys,1,1,1,0,0},
|
||||
{"psetex",psetexCommand,4,"wm",0,noPreloadGetKeys,1,1,1,0,0},
|
||||
{"get",getCommand,2,"rF",0,NULL,1,1,1,0,0},
|
||||
{"set",setCommand,-3,"wm",0,NULL,1,1,1,0,0},
|
||||
{"setnx",setnxCommand,3,"wmF",0,NULL,1,1,1,0,0},
|
||||
{"setex",setexCommand,4,"wm",0,NULL,1,1,1,0,0},
|
||||
{"psetex",psetexCommand,4,"wm",0,NULL,1,1,1,0,0},
|
||||
{"append",appendCommand,3,"wm",0,NULL,1,1,1,0,0},
|
||||
{"strlen",strlenCommand,2,"r",0,NULL,1,1,1,0,0},
|
||||
{"del",delCommand,-2,"w",0,noPreloadGetKeys,1,-1,1,0,0},
|
||||
{"exists",existsCommand,2,"r",0,NULL,1,1,1,0,0},
|
||||
{"strlen",strlenCommand,2,"rF",0,NULL,1,1,1,0,0},
|
||||
{"del",delCommand,-2,"w",0,NULL,1,-1,1,0,0},
|
||||
{"exists",existsCommand,2,"rF",0,NULL,1,1,1,0,0},
|
||||
{"setbit",setbitCommand,4,"wm",0,NULL,1,1,1,0,0},
|
||||
{"getbit",getbitCommand,3,"r",0,NULL,1,1,1,0,0},
|
||||
{"getbit",getbitCommand,3,"rF",0,NULL,1,1,1,0,0},
|
||||
{"setrange",setrangeCommand,4,"wm",0,NULL,1,1,1,0,0},
|
||||
{"getrange",getrangeCommand,4,"r",0,NULL,1,1,1,0,0},
|
||||
{"substr",getrangeCommand,4,"r",0,NULL,1,1,1,0,0},
|
||||
{"incr",incrCommand,2,"wm",0,NULL,1,1,1,0,0},
|
||||
{"decr",decrCommand,2,"wm",0,NULL,1,1,1,0,0},
|
||||
{"incr",incrCommand,2,"wmF",0,NULL,1,1,1,0,0},
|
||||
{"decr",decrCommand,2,"wmF",0,NULL,1,1,1,0,0},
|
||||
{"mget",mgetCommand,-2,"r",0,NULL,1,-1,1,0,0},
|
||||
{"rpush",rpushCommand,-3,"wm",0,NULL,1,1,1,0,0},
|
||||
{"lpush",lpushCommand,-3,"wm",0,NULL,1,1,1,0,0},
|
||||
{"rpushx",rpushxCommand,3,"wm",0,NULL,1,1,1,0,0},
|
||||
{"lpushx",lpushxCommand,3,"wm",0,NULL,1,1,1,0,0},
|
||||
{"rpush",rpushCommand,-3,"wmF",0,NULL,1,1,1,0,0},
|
||||
{"lpush",lpushCommand,-3,"wmF",0,NULL,1,1,1,0,0},
|
||||
{"rpushx",rpushxCommand,3,"wmF",0,NULL,1,1,1,0,0},
|
||||
{"lpushx",lpushxCommand,3,"wmF",0,NULL,1,1,1,0,0},
|
||||
{"linsert",linsertCommand,5,"wm",0,NULL,1,1,1,0,0},
|
||||
{"rpop",rpopCommand,2,"w",0,NULL,1,1,1,0,0},
|
||||
{"lpop",lpopCommand,2,"w",0,NULL,1,1,1,0,0},
|
||||
{"rpop",rpopCommand,2,"wF",0,NULL,1,1,1,0,0},
|
||||
{"lpop",lpopCommand,2,"wF",0,NULL,1,1,1,0,0},
|
||||
{"brpop",brpopCommand,-3,"ws",0,NULL,1,1,1,0,0},
|
||||
{"brpoplpush",brpoplpushCommand,4,"wms",0,NULL,1,2,1,0,0},
|
||||
{"blpop",blpopCommand,-3,"ws",0,NULL,1,-2,1,0,0},
|
||||
{"llen",llenCommand,2,"r",0,NULL,1,1,1,0,0},
|
||||
{"llen",llenCommand,2,"rF",0,NULL,1,1,1,0,0},
|
||||
{"lindex",lindexCommand,3,"r",0,NULL,1,1,1,0,0},
|
||||
{"lset",lsetCommand,4,"wm",0,NULL,1,1,1,0,0},
|
||||
{"lrange",lrangeCommand,4,"r",0,NULL,1,1,1,0,0},
|
||||
{"ltrim",ltrimCommand,4,"w",0,NULL,1,1,1,0,0},
|
||||
{"lrem",lremCommand,4,"w",0,NULL,1,1,1,0,0},
|
||||
{"rpoplpush",rpoplpushCommand,3,"wm",0,NULL,1,2,1,0,0},
|
||||
{"sadd",saddCommand,-3,"wm",0,NULL,1,1,1,0,0},
|
||||
{"srem",sremCommand,-3,"w",0,NULL,1,1,1,0,0},
|
||||
{"smove",smoveCommand,4,"w",0,NULL,1,2,1,0,0},
|
||||
{"sismember",sismemberCommand,3,"r",0,NULL,1,1,1,0,0},
|
||||
{"scard",scardCommand,2,"r",0,NULL,1,1,1,0,0},
|
||||
{"spop",spopCommand,2,"wRs",0,NULL,1,1,1,0,0},
|
||||
{"sadd",saddCommand,-3,"wmF",0,NULL,1,1,1,0,0},
|
||||
{"srem",sremCommand,-3,"wF",0,NULL,1,1,1,0,0},
|
||||
{"smove",smoveCommand,4,"wF",0,NULL,1,2,1,0,0},
|
||||
{"sismember",sismemberCommand,3,"rF",0,NULL,1,1,1,0,0},
|
||||
{"scard",scardCommand,2,"rF",0,NULL,1,1,1,0,0},
|
||||
{"spop",spopCommand,2,"wRsF",0,NULL,1,1,1,0,0},
|
||||
{"srandmember",srandmemberCommand,-2,"rR",0,NULL,1,1,1,0,0},
|
||||
{"sinter",sinterCommand,-2,"rS",0,NULL,1,-1,1,0,0},
|
||||
{"sinterstore",sinterstoreCommand,-3,"wm",0,NULL,1,-1,1,0,0},
|
||||
@@ -163,9 +166,9 @@ struct redisCommand redisCommandTable[] = {
|
||||
{"sdiffstore",sdiffstoreCommand,-3,"wm",0,NULL,1,-1,1,0,0},
|
||||
{"smembers",sinterCommand,2,"rS",0,NULL,1,1,1,0,0},
|
||||
{"sscan",sscanCommand,-3,"rR",0,NULL,1,1,1,0,0},
|
||||
{"zadd",zaddCommand,-4,"wm",0,NULL,1,1,1,0,0},
|
||||
{"zincrby",zincrbyCommand,4,"wm",0,NULL,1,1,1,0,0},
|
||||
{"zrem",zremCommand,-3,"w",0,NULL,1,1,1,0,0},
|
||||
{"zadd",zaddCommand,-4,"wmF",0,NULL,1,1,1,0,0},
|
||||
{"zincrby",zincrbyCommand,4,"wmF",0,NULL,1,1,1,0,0},
|
||||
{"zrem",zremCommand,-3,"wF",0,NULL,1,1,1,0,0},
|
||||
{"zremrangebyscore",zremrangebyscoreCommand,4,"w",0,NULL,1,1,1,0,0},
|
||||
{"zremrangebyrank",zremrangebyrankCommand,4,"w",0,NULL,1,1,1,0,0},
|
||||
{"zremrangebylex",zremrangebylexCommand,4,"w",0,NULL,1,1,1,0,0},
|
||||
@@ -176,58 +179,58 @@ struct redisCommand redisCommandTable[] = {
|
||||
{"zrevrangebyscore",zrevrangebyscoreCommand,-4,"r",0,NULL,1,1,1,0,0},
|
||||
{"zrangebylex",zrangebylexCommand,-4,"r",0,NULL,1,1,1,0,0},
|
||||
{"zrevrangebylex",zrevrangebylexCommand,-4,"r",0,NULL,1,1,1,0,0},
|
||||
{"zcount",zcountCommand,4,"r",0,NULL,1,1,1,0,0},
|
||||
{"zlexcount",zlexcountCommand,4,"r",0,NULL,1,1,1,0,0},
|
||||
{"zcount",zcountCommand,4,"rF",0,NULL,1,1,1,0,0},
|
||||
{"zlexcount",zlexcountCommand,4,"rF",0,NULL,1,1,1,0,0},
|
||||
{"zrevrange",zrevrangeCommand,-4,"r",0,NULL,1,1,1,0,0},
|
||||
{"zcard",zcardCommand,2,"r",0,NULL,1,1,1,0,0},
|
||||
{"zscore",zscoreCommand,3,"r",0,NULL,1,1,1,0,0},
|
||||
{"zrank",zrankCommand,3,"r",0,NULL,1,1,1,0,0},
|
||||
{"zrevrank",zrevrankCommand,3,"r",0,NULL,1,1,1,0,0},
|
||||
{"zcard",zcardCommand,2,"rF",0,NULL,1,1,1,0,0},
|
||||
{"zscore",zscoreCommand,3,"rF",0,NULL,1,1,1,0,0},
|
||||
{"zrank",zrankCommand,3,"rF",0,NULL,1,1,1,0,0},
|
||||
{"zrevrank",zrevrankCommand,3,"rF",0,NULL,1,1,1,0,0},
|
||||
{"zscan",zscanCommand,-3,"rR",0,NULL,1,1,1,0,0},
|
||||
{"hset",hsetCommand,4,"wm",0,NULL,1,1,1,0,0},
|
||||
{"hsetnx",hsetnxCommand,4,"wm",0,NULL,1,1,1,0,0},
|
||||
{"hget",hgetCommand,3,"r",0,NULL,1,1,1,0,0},
|
||||
{"hset",hsetCommand,4,"wmF",0,NULL,1,1,1,0,0},
|
||||
{"hsetnx",hsetnxCommand,4,"wmF",0,NULL,1,1,1,0,0},
|
||||
{"hget",hgetCommand,3,"rF",0,NULL,1,1,1,0,0},
|
||||
{"hmset",hmsetCommand,-4,"wm",0,NULL,1,1,1,0,0},
|
||||
{"hmget",hmgetCommand,-3,"r",0,NULL,1,1,1,0,0},
|
||||
{"hincrby",hincrbyCommand,4,"wm",0,NULL,1,1,1,0,0},
|
||||
{"hincrbyfloat",hincrbyfloatCommand,4,"wm",0,NULL,1,1,1,0,0},
|
||||
{"hdel",hdelCommand,-3,"w",0,NULL,1,1,1,0,0},
|
||||
{"hlen",hlenCommand,2,"r",0,NULL,1,1,1,0,0},
|
||||
{"hincrby",hincrbyCommand,4,"wmF",0,NULL,1,1,1,0,0},
|
||||
{"hincrbyfloat",hincrbyfloatCommand,4,"wmF",0,NULL,1,1,1,0,0},
|
||||
{"hdel",hdelCommand,-3,"wF",0,NULL,1,1,1,0,0},
|
||||
{"hlen",hlenCommand,2,"rF",0,NULL,1,1,1,0,0},
|
||||
{"hkeys",hkeysCommand,2,"rS",0,NULL,1,1,1,0,0},
|
||||
{"hvals",hvalsCommand,2,"rS",0,NULL,1,1,1,0,0},
|
||||
{"hgetall",hgetallCommand,2,"r",0,NULL,1,1,1,0,0},
|
||||
{"hexists",hexistsCommand,3,"r",0,NULL,1,1,1,0,0},
|
||||
{"hexists",hexistsCommand,3,"rF",0,NULL,1,1,1,0,0},
|
||||
{"hscan",hscanCommand,-3,"rR",0,NULL,1,1,1,0,0},
|
||||
{"incrby",incrbyCommand,3,"wm",0,NULL,1,1,1,0,0},
|
||||
{"decrby",decrbyCommand,3,"wm",0,NULL,1,1,1,0,0},
|
||||
{"incrbyfloat",incrbyfloatCommand,3,"wm",0,NULL,1,1,1,0,0},
|
||||
{"incrby",incrbyCommand,3,"wmF",0,NULL,1,1,1,0,0},
|
||||
{"decrby",decrbyCommand,3,"wmF",0,NULL,1,1,1,0,0},
|
||||
{"incrbyfloat",incrbyfloatCommand,3,"wmF",0,NULL,1,1,1,0,0},
|
||||
{"getset",getsetCommand,3,"wm",0,NULL,1,1,1,0,0},
|
||||
{"mset",msetCommand,-3,"wm",0,NULL,1,-1,2,0,0},
|
||||
{"msetnx",msetnxCommand,-3,"wm",0,NULL,1,-1,2,0,0},
|
||||
{"randomkey",randomkeyCommand,1,"rR",0,NULL,0,0,0,0,0},
|
||||
{"select",selectCommand,2,"rl",0,NULL,0,0,0,0,0},
|
||||
{"move",moveCommand,3,"w",0,NULL,1,1,1,0,0},
|
||||
{"rename",renameCommand,3,"w",0,renameGetKeys,1,2,1,0,0},
|
||||
{"renamenx",renamenxCommand,3,"w",0,renameGetKeys,1,2,1,0,0},
|
||||
{"expire",expireCommand,3,"w",0,NULL,1,1,1,0,0},
|
||||
{"expireat",expireatCommand,3,"w",0,NULL,1,1,1,0,0},
|
||||
{"pexpire",pexpireCommand,3,"w",0,NULL,1,1,1,0,0},
|
||||
{"pexpireat",pexpireatCommand,3,"w",0,NULL,1,1,1,0,0},
|
||||
{"select",selectCommand,2,"rlF",0,NULL,0,0,0,0,0},
|
||||
{"move",moveCommand,3,"wF",0,NULL,1,1,1,0,0},
|
||||
{"rename",renameCommand,3,"w",0,NULL,1,2,1,0,0},
|
||||
{"renamenx",renamenxCommand,3,"wF",0,NULL,1,2,1,0,0},
|
||||
{"expire",expireCommand,3,"wF",0,NULL,1,1,1,0,0},
|
||||
{"expireat",expireatCommand,3,"wF",0,NULL,1,1,1,0,0},
|
||||
{"pexpire",pexpireCommand,3,"wF",0,NULL,1,1,1,0,0},
|
||||
{"pexpireat",pexpireatCommand,3,"wF",0,NULL,1,1,1,0,0},
|
||||
{"keys",keysCommand,2,"rS",0,NULL,0,0,0,0,0},
|
||||
{"scan",scanCommand,-2,"rR",0,NULL,0,0,0,0,0},
|
||||
{"dbsize",dbsizeCommand,1,"r",0,NULL,0,0,0,0,0},
|
||||
{"auth",authCommand,2,"rslt",0,NULL,0,0,0,0,0},
|
||||
{"ping",pingCommand,1,"rt",0,NULL,0,0,0,0,0},
|
||||
{"echo",echoCommand,2,"r",0,NULL,0,0,0,0,0},
|
||||
{"dbsize",dbsizeCommand,1,"rF",0,NULL,0,0,0,0,0},
|
||||
{"auth",authCommand,2,"rsltF",0,NULL,0,0,0,0,0},
|
||||
{"ping",pingCommand,-1,"rtF",0,NULL,0,0,0,0,0},
|
||||
{"echo",echoCommand,2,"rF",0,NULL,0,0,0,0,0},
|
||||
{"save",saveCommand,1,"ars",0,NULL,0,0,0,0,0},
|
||||
{"bgsave",bgsaveCommand,1,"ar",0,NULL,0,0,0,0,0},
|
||||
{"bgrewriteaof",bgrewriteaofCommand,1,"ar",0,NULL,0,0,0,0,0},
|
||||
{"shutdown",shutdownCommand,-1,"arlt",0,NULL,0,0,0,0,0},
|
||||
{"lastsave",lastsaveCommand,1,"rR",0,NULL,0,0,0,0,0},
|
||||
{"type",typeCommand,2,"r",0,NULL,1,1,1,0,0},
|
||||
{"multi",multiCommand,1,"rs",0,NULL,0,0,0,0,0},
|
||||
{"lastsave",lastsaveCommand,1,"rRF",0,NULL,0,0,0,0,0},
|
||||
{"type",typeCommand,2,"rF",0,NULL,1,1,1,0,0},
|
||||
{"multi",multiCommand,1,"rsF",0,NULL,0,0,0,0,0},
|
||||
{"exec",execCommand,1,"sM",0,NULL,0,0,0,0,0},
|
||||
{"discard",discardCommand,1,"rs",0,NULL,0,0,0,0,0},
|
||||
{"discard",discardCommand,1,"rsF",0,NULL,0,0,0,0,0},
|
||||
{"sync",syncCommand,1,"ars",0,NULL,0,0,0,0,0},
|
||||
{"psync",syncCommand,3,"ars",0,NULL,0,0,0,0,0},
|
||||
{"replconf",replconfCommand,-1,"arslt",0,NULL,0,0,0,0,0},
|
||||
@@ -236,9 +239,9 @@ struct redisCommand redisCommandTable[] = {
|
||||
{"sort",sortCommand,-2,"wm",0,NULL,1,1,1,0,0},
|
||||
{"info",infoCommand,-1,"rlt",0,NULL,0,0,0,0,0},
|
||||
{"monitor",monitorCommand,1,"ars",0,NULL,0,0,0,0,0},
|
||||
{"ttl",ttlCommand,2,"r",0,NULL,1,1,1,0,0},
|
||||
{"pttl",pttlCommand,2,"r",0,NULL,1,1,1,0,0},
|
||||
{"persist",persistCommand,2,"w",0,NULL,1,1,1,0,0},
|
||||
{"ttl",ttlCommand,2,"rF",0,NULL,1,1,1,0,0},
|
||||
{"pttl",pttlCommand,2,"rF",0,NULL,1,1,1,0,0},
|
||||
{"persist",persistCommand,2,"wF",0,NULL,1,1,1,0,0},
|
||||
{"slaveof",slaveofCommand,3,"ast",0,NULL,0,0,0,0,0},
|
||||
{"role",roleCommand,1,"last",0,NULL,0,0,0,0,0},
|
||||
{"debug",debugCommand,-2,"as",0,NULL,0,0,0,0,0},
|
||||
@@ -247,28 +250,30 @@ struct redisCommand redisCommandTable[] = {
|
||||
{"unsubscribe",unsubscribeCommand,-1,"rpslt",0,NULL,0,0,0,0,0},
|
||||
{"psubscribe",psubscribeCommand,-2,"rpslt",0,NULL,0,0,0,0,0},
|
||||
{"punsubscribe",punsubscribeCommand,-1,"rpslt",0,NULL,0,0,0,0,0},
|
||||
{"publish",publishCommand,3,"pltr",0,NULL,0,0,0,0,0},
|
||||
{"publish",publishCommand,3,"pltrF",0,NULL,0,0,0,0,0},
|
||||
{"pubsub",pubsubCommand,-2,"pltrR",0,NULL,0,0,0,0,0},
|
||||
{"watch",watchCommand,-2,"rs",0,noPreloadGetKeys,1,-1,1,0,0},
|
||||
{"unwatch",unwatchCommand,1,"rs",0,NULL,0,0,0,0,0},
|
||||
{"watch",watchCommand,-2,"rsF",0,NULL,1,-1,1,0,0},
|
||||
{"unwatch",unwatchCommand,1,"rsF",0,NULL,0,0,0,0,0},
|
||||
{"restore",restoreCommand,4,"awm",0,NULL,1,1,1,0,0},
|
||||
{"migrate",migrateCommand,6,"aw",0,NULL,0,0,0,0,0},
|
||||
{"dump",dumpCommand,2,"ar",0,NULL,1,1,1,0,0},
|
||||
{"object",objectCommand,-2,"r",0,NULL,2,2,2,0,0},
|
||||
{"client",clientCommand,-2,"ar",0,NULL,0,0,0,0,0},
|
||||
{"object",objectCommand,3,"r",0,NULL,2,2,2,0,0},
|
||||
{"client",clientCommand,-2,"ars",0,NULL,0,0,0,0,0},
|
||||
{"eval",evalCommand,-3,"s",0,zunionInterGetKeys,0,0,0,0,0},
|
||||
{"evalsha",evalShaCommand,-3,"s",0,zunionInterGetKeys,0,0,0,0,0},
|
||||
{"slowlog",slowlogCommand,-2,"r",0,NULL,0,0,0,0,0},
|
||||
{"script",scriptCommand,-2,"ras",0,NULL,0,0,0,0,0},
|
||||
{"time",timeCommand,1,"rR",0,NULL,0,0,0,0,0},
|
||||
{"time",timeCommand,1,"rRF",0,NULL,0,0,0,0,0},
|
||||
{"bitop",bitopCommand,-4,"wm",0,NULL,2,-1,1,0,0},
|
||||
{"bitcount",bitcountCommand,-2,"r",0,NULL,1,1,1,0,0},
|
||||
{"bitpos",bitposCommand,-3,"r",0,NULL,1,1,1,0,0},
|
||||
{"command",commandCommand,0,"rlt",0,NULL,0,0,0,0,0},
|
||||
{"pfselftest",pfselftestCommand,1,"r",0,NULL,0,0,0,0,0},
|
||||
{"pfadd",pfaddCommand,-2,"wm",0,NULL,1,1,1,0,0},
|
||||
{"pfadd",pfaddCommand,-2,"wmF",0,NULL,1,1,1,0,0},
|
||||
{"pfcount",pfcountCommand,-2,"w",0,NULL,1,1,1,0,0},
|
||||
{"pfmerge",pfmergeCommand,-2,"wm",0,NULL,1,-1,1,0,0},
|
||||
{"pfdebug",pfdebugCommand,-3,"w",0,NULL,0,0,0,0,0}
|
||||
{"pfdebug",pfdebugCommand,-3,"w",0,NULL,0,0,0,0,0},
|
||||
{"latency",latencyCommand,-2,"arslt",0,NULL,0,0,0,0,0}
|
||||
};
|
||||
|
||||
/*============================ Utility functions ============================ */
|
||||
@@ -701,8 +706,8 @@ void activeExpireCycle(int type) {
|
||||
static int timelimit_exit = 0; /* Time limit hit in previous call? */
|
||||
static long long last_fast_cycle = 0; /* When last fast cycle ran. */
|
||||
|
||||
unsigned int j, iteration = 0;
|
||||
unsigned int dbs_per_call = REDIS_DBCRON_DBS_PER_CALL;
|
||||
int j, iteration = 0;
|
||||
int dbs_per_call = REDIS_DBCRON_DBS_PER_CALL;
|
||||
long long start = ustime(), timelimit;
|
||||
|
||||
if (type == ACTIVE_EXPIRE_CYCLE_FAST) {
|
||||
@@ -799,10 +804,11 @@ void activeExpireCycle(int type) {
|
||||
* expire. So after a given amount of milliseconds return to the
|
||||
* caller waiting for the other active expire cycle. */
|
||||
iteration++;
|
||||
if ((iteration & 0xf) == 0 && /* check once every 16 iterations. */
|
||||
(ustime()-start) > timelimit)
|
||||
{
|
||||
timelimit_exit = 1;
|
||||
if ((iteration & 0xf) == 0) { /* check once every 16 iterations. */
|
||||
long long elapsed = ustime()-start;
|
||||
|
||||
latencyAddSampleIfNeeded("expire-cycle",elapsed/1000);
|
||||
if (elapsed > timelimit) timelimit_exit = 1;
|
||||
}
|
||||
if (timelimit_exit) return;
|
||||
/* We don't repeat the cycle if there are less than 25% of keys
|
||||
@@ -849,8 +855,7 @@ int clientsCronHandleTimeout(redisClient *c) {
|
||||
!(c->flags & REDIS_SLAVE) && /* no timeout for slaves */
|
||||
!(c->flags & REDIS_MASTER) && /* no timeout for masters */
|
||||
!(c->flags & REDIS_BLOCKED) && /* no timeout for BLPOP */
|
||||
dictSize(c->pubsub_channels) == 0 && /* no timeout for pubsub */
|
||||
listLength(c->pubsub_patterns) == 0 &&
|
||||
!(c->flags & REDIS_PUBSUB) && /* no timeout for Pub/Sub clients */
|
||||
(now - c->lastinteraction > server.maxidletime))
|
||||
{
|
||||
redisLog(REDIS_VERBOSE,"Closing idle client");
|
||||
@@ -938,8 +943,8 @@ void databasesCron(void) {
|
||||
* cron loop iteration. */
|
||||
static unsigned int resize_db = 0;
|
||||
static unsigned int rehash_db = 0;
|
||||
unsigned int dbs_per_call = REDIS_DBCRON_DBS_PER_CALL;
|
||||
unsigned int j;
|
||||
int dbs_per_call = REDIS_DBCRON_DBS_PER_CALL;
|
||||
int j;
|
||||
|
||||
/* Don't test more DBs than we have. */
|
||||
if (dbs_per_call > server.dbnum) dbs_per_call = server.dbnum;
|
||||
@@ -1085,7 +1090,7 @@ int serverCron(struct aeEventLoop *eventLoop, long long id, void *clientData) {
|
||||
if ((pid = wait3(&statloc,WNOHANG,NULL)) != 0) {
|
||||
int exitcode = WEXITSTATUS(statloc);
|
||||
int bysignal = 0;
|
||||
|
||||
|
||||
if (WIFSIGNALED(statloc)) bysignal = WTERMSIG(statloc);
|
||||
|
||||
if (pid == server.rdb_child_pid) {
|
||||
@@ -1291,7 +1296,7 @@ void createSharedObjects(void) {
|
||||
shared.maxstring = createStringObject("maxstring",9);
|
||||
}
|
||||
|
||||
void initServerConfig() {
|
||||
void initServerConfig(void) {
|
||||
int j;
|
||||
|
||||
getRandomHexChars(server.runid,REDIS_RUN_ID_SIZE);
|
||||
@@ -1335,6 +1340,7 @@ void initServerConfig() {
|
||||
server.aof_selected_db = -1; /* Make sure the first time will not match */
|
||||
server.aof_flush_postponed_start = 0;
|
||||
server.aof_rewrite_incremental_fsync = REDIS_DEFAULT_AOF_REWRITE_INCREMENTAL_FSYNC;
|
||||
server.aof_load_truncated = REDIS_DEFAULT_AOF_LOAD_TRUNCATED;
|
||||
server.pidfile = zstrdup(REDIS_DEFAULT_PID_FILE);
|
||||
server.rdb_filename = zstrdup(REDIS_DEFAULT_RDB_FILENAME);
|
||||
server.aof_filename = zstrdup(REDIS_DEFAULT_AOF_FILENAME);
|
||||
@@ -1421,11 +1427,14 @@ void initServerConfig() {
|
||||
server.lpushCommand = lookupCommandByCString("lpush");
|
||||
server.lpopCommand = lookupCommandByCString("lpop");
|
||||
server.rpopCommand = lookupCommandByCString("rpop");
|
||||
|
||||
|
||||
/* Slow log */
|
||||
server.slowlog_log_slower_than = REDIS_SLOWLOG_LOG_SLOWER_THAN;
|
||||
server.slowlog_max_len = REDIS_SLOWLOG_MAX_LEN;
|
||||
|
||||
/* Latency monitor */
|
||||
server.latency_monitor_threshold = REDIS_DEFAULT_LATENCY_MONITOR_THRESHOLD;
|
||||
|
||||
/* Debugging */
|
||||
server.assert_failed = "<no assertion failed>";
|
||||
server.assert_file = "<no file>";
|
||||
@@ -1463,7 +1472,7 @@ void adjustOpenFilesLimit(void) {
|
||||
* to the higher value supported less than maxfiles. */
|
||||
f = maxfiles;
|
||||
while(f > oldlimit) {
|
||||
int decr_step = 16;
|
||||
rlim_t decr_step = 16;
|
||||
|
||||
limit.rlim_cur = f;
|
||||
limit.rlim_max = f;
|
||||
@@ -1591,6 +1600,7 @@ void resetServerStats(void) {
|
||||
server.stat_keyspace_misses = 0;
|
||||
server.stat_keyspace_hits = 0;
|
||||
server.stat_fork_time = 0;
|
||||
server.stat_fork_rate = 0;
|
||||
server.stat_rejected_conn = 0;
|
||||
server.stat_sync_full = 0;
|
||||
server.stat_sync_partial_ok = 0;
|
||||
@@ -1601,7 +1611,7 @@ void resetServerStats(void) {
|
||||
server.ops_sec_last_sample_ops = 0;
|
||||
}
|
||||
|
||||
void initServer() {
|
||||
void initServer(void) {
|
||||
int j;
|
||||
|
||||
signal(SIGHUP, SIG_IGN);
|
||||
@@ -1729,6 +1739,7 @@ void initServer() {
|
||||
replicationScriptCacheInit();
|
||||
scriptingInit();
|
||||
slowlogInit();
|
||||
latencyMonitorInit();
|
||||
bioInit();
|
||||
}
|
||||
|
||||
@@ -1756,6 +1767,7 @@ void populateCommandTable(void) {
|
||||
case 'l': c->flags |= REDIS_CMD_LOADING; break;
|
||||
case 't': c->flags |= REDIS_CMD_STALE; break;
|
||||
case 'M': c->flags |= REDIS_CMD_SKIP_MONITOR; break;
|
||||
case 'F': c->flags |= REDIS_CMD_FAST; break;
|
||||
default: redisPanic("Unsupported command flag"); break;
|
||||
}
|
||||
f++;
|
||||
@@ -1902,6 +1914,7 @@ void call(redisClient *c, int flags) {
|
||||
c->cmd->proc(c);
|
||||
duration = ustime()-start;
|
||||
dirty = server.dirty-dirty;
|
||||
if (dirty < 0) dirty = 0;
|
||||
|
||||
/* When EVAL is called loading the AOF we don't want commands called
|
||||
* from Lua to go into the slowlog or to populate statistics. */
|
||||
@@ -1920,8 +1933,12 @@ void call(redisClient *c, int flags) {
|
||||
|
||||
/* Log the command into the Slow log if needed, and populate the
|
||||
* per-command statistics that we show in INFO commandstats. */
|
||||
if (flags & REDIS_CALL_SLOWLOG && c->cmd->proc != execCommand)
|
||||
if (flags & REDIS_CALL_SLOWLOG && c->cmd->proc != execCommand) {
|
||||
char *latency_event = (c->cmd->flags & REDIS_CMD_FAST) ?
|
||||
"fast-command" : "command";
|
||||
latencyAddSampleIfNeeded(latency_event,duration/1000);
|
||||
slowlogPushEntryIfNeeded(c->argv,c->argc,duration);
|
||||
}
|
||||
if (flags & REDIS_CALL_STATS) {
|
||||
c->cmd->microseconds += duration;
|
||||
c->cmd->calls++;
|
||||
@@ -2061,8 +2078,8 @@ int processCommand(redisClient *c) {
|
||||
}
|
||||
|
||||
/* Only allow SUBSCRIBE and UNSUBSCRIBE in the context of Pub/Sub */
|
||||
if ((dictSize(c->pubsub_channels) > 0 || listLength(c->pubsub_patterns) > 0)
|
||||
&&
|
||||
if (c->flags & REDIS_PUBSUB &&
|
||||
c->cmd->proc != pingCommand &&
|
||||
c->cmd->proc != subscribeCommand &&
|
||||
c->cmd->proc != unsubscribeCommand &&
|
||||
c->cmd->proc != psubscribeCommand &&
|
||||
@@ -2207,9 +2224,9 @@ int time_independent_strcmp(char *a, char *b) {
|
||||
* a or b are fixed (our password) length, and the difference is only
|
||||
* relative to the length of the user provided string, so no information
|
||||
* leak is possible in the following two lines of code. */
|
||||
int alen = strlen(a);
|
||||
int blen = strlen(b);
|
||||
int j;
|
||||
unsigned int alen = strlen(a);
|
||||
unsigned int blen = strlen(b);
|
||||
unsigned int j;
|
||||
int diff = 0;
|
||||
|
||||
/* We can't compare strings longer than our static buffers.
|
||||
@@ -2246,8 +2263,29 @@ void authCommand(redisClient *c) {
|
||||
}
|
||||
}
|
||||
|
||||
/* The PING command. It works in a different way if the client is in
|
||||
* in Pub/Sub mode. */
|
||||
void pingCommand(redisClient *c) {
|
||||
addReply(c,shared.pong);
|
||||
/* The command takes zero or one arguments. */
|
||||
if (c->argc > 2) {
|
||||
addReplyErrorFormat(c,"wrong number of arguments for '%s' command",
|
||||
c->cmd->name);
|
||||
return;
|
||||
}
|
||||
|
||||
if (c->flags & REDIS_PUBSUB) {
|
||||
addReply(c,shared.mbulkhdr[2]);
|
||||
addReplyBulkCBuffer(c,"pong",4);
|
||||
if (c->argc == 1)
|
||||
addReplyBulkCBuffer(c,"",0);
|
||||
else
|
||||
addReplyBulk(c,c->argv[1]);
|
||||
} else {
|
||||
if (c->argc == 1)
|
||||
addReply(c,shared.pong);
|
||||
else
|
||||
addReplyBulk(c,c->argv[1]);
|
||||
}
|
||||
}
|
||||
|
||||
void echoCommand(redisClient *c) {
|
||||
@@ -2265,6 +2303,78 @@ void timeCommand(redisClient *c) {
|
||||
addReplyBulkLongLong(c,tv.tv_usec);
|
||||
}
|
||||
|
||||
|
||||
/* Helper function for addReplyCommand() to output flags. */
|
||||
int addReplyCommandFlag(redisClient *c, struct redisCommand *cmd, int f, char *reply) {
|
||||
if (cmd->flags & f) {
|
||||
addReplyStatus(c, reply);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Output the representation of a Redis command. Used by the COMMAND command. */
|
||||
void addReplyCommand(redisClient *c, struct redisCommand *cmd) {
|
||||
if (!cmd) {
|
||||
addReply(c, shared.nullbulk);
|
||||
} else {
|
||||
/* We are adding: command name, arg count, flags, first, last, offset */
|
||||
addReplyMultiBulkLen(c, 6);
|
||||
addReplyBulkCString(c, cmd->name);
|
||||
addReplyLongLong(c, cmd->arity);
|
||||
|
||||
int flagcount = 0;
|
||||
void *flaglen = addDeferredMultiBulkLength(c);
|
||||
flagcount += addReplyCommandFlag(c,cmd,REDIS_CMD_WRITE, "write");
|
||||
flagcount += addReplyCommandFlag(c,cmd,REDIS_CMD_READONLY, "readonly");
|
||||
flagcount += addReplyCommandFlag(c,cmd,REDIS_CMD_DENYOOM, "denyoom");
|
||||
flagcount += addReplyCommandFlag(c,cmd,REDIS_CMD_ADMIN, "admin");
|
||||
flagcount += addReplyCommandFlag(c,cmd,REDIS_CMD_PUBSUB, "pubsub");
|
||||
flagcount += addReplyCommandFlag(c,cmd,REDIS_CMD_NOSCRIPT, "noscript");
|
||||
flagcount += addReplyCommandFlag(c,cmd,REDIS_CMD_RANDOM, "random");
|
||||
flagcount += addReplyCommandFlag(c,cmd,REDIS_CMD_SORT_FOR_SCRIPT,"sort_for_script");
|
||||
flagcount += addReplyCommandFlag(c,cmd,REDIS_CMD_LOADING, "loading");
|
||||
flagcount += addReplyCommandFlag(c,cmd,REDIS_CMD_STALE, "stale");
|
||||
flagcount += addReplyCommandFlag(c,cmd,REDIS_CMD_SKIP_MONITOR, "skip_monitor");
|
||||
flagcount += addReplyCommandFlag(c,cmd,REDIS_CMD_FAST, "fast");
|
||||
if (cmd->getkeys_proc) {
|
||||
addReplyStatus(c, "movablekeys");
|
||||
flagcount += 1;
|
||||
}
|
||||
setDeferredMultiBulkLength(c, flaglen, flagcount);
|
||||
|
||||
addReplyLongLong(c, cmd->firstkey);
|
||||
addReplyLongLong(c, cmd->lastkey);
|
||||
addReplyLongLong(c, cmd->keystep);
|
||||
}
|
||||
}
|
||||
|
||||
/* COMMAND <subcommand> <args> */
|
||||
void commandCommand(redisClient *c) {
|
||||
dictIterator *di;
|
||||
dictEntry *de;
|
||||
|
||||
if (c->argc == 1) {
|
||||
addReplyMultiBulkLen(c, dictSize(server.commands));
|
||||
di = dictGetIterator(server.commands);
|
||||
while ((de = dictNext(di)) != NULL) {
|
||||
addReplyCommand(c, dictGetVal(de));
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
} else if (!strcasecmp(c->argv[1]->ptr, "info")) {
|
||||
int i;
|
||||
addReplyMultiBulkLen(c, c->argc-2);
|
||||
for (i = 2; i < c->argc; i++) {
|
||||
addReplyCommand(c, dictFetchValue(server.commands, c->argv[i]->ptr));
|
||||
}
|
||||
} else if (!strcasecmp(c->argv[1]->ptr, "count") && c->argc == 2) {
|
||||
addReplyLongLong(c, dictSize(server.commands));
|
||||
} else {
|
||||
addReplyError(c, "Unknown subcommand or wrong number of arguments.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/* Convert an amount of bytes into a human readable string in the form
|
||||
* of 100B, 2G, 100M, 4K, and so forth. */
|
||||
void bytesToHuman(char *s, unsigned long long n) {
|
||||
@@ -2283,6 +2393,15 @@ void bytesToHuman(char *s, unsigned long long n) {
|
||||
} else if (n < (1024LL*1024*1024*1024)) {
|
||||
d = (double)n/(1024LL*1024*1024);
|
||||
sprintf(s,"%.2fG",d);
|
||||
} else if (n < (1024LL*1024*1024*1024*1024)) {
|
||||
d = (double)n/(1024LL*1024*1024*1024);
|
||||
sprintf(s,"%.2fT",d);
|
||||
} else if (n < (1024LL*1024*1024*1024*1024*1024)) {
|
||||
d = (double)n/(1024LL*1024*1024*1024*1024);
|
||||
sprintf(s,"%.2fP",d);
|
||||
} else {
|
||||
/* Let's hope we never need this */
|
||||
sprintf(s,"%lluB",n);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2298,10 +2417,9 @@ sds genRedisInfoString(char *section) {
|
||||
int allsections = 0, defsections = 0;
|
||||
int sections = 0;
|
||||
|
||||
if (section) {
|
||||
allsections = strcasecmp(section,"all") == 0;
|
||||
defsections = strcasecmp(section,"default") == 0;
|
||||
}
|
||||
if (section == NULL) section = "default";
|
||||
allsections = strcasecmp(section,"all") == 0;
|
||||
defsections = strcasecmp(section,"default") == 0;
|
||||
|
||||
getrusage(RUSAGE_SELF, &self_ru);
|
||||
getrusage(RUSAGE_CHILDREN, &c_ru);
|
||||
@@ -2315,7 +2433,7 @@ sds genRedisInfoString(char *section) {
|
||||
|
||||
if (server.sentinel_mode) mode = "sentinel";
|
||||
else mode = "standalone";
|
||||
|
||||
|
||||
if (sections++) info = sdscat(info,"\r\n");
|
||||
|
||||
if (call_uname) {
|
||||
@@ -2749,6 +2867,7 @@ void monitorCommand(redisClient *c) {
|
||||
int freeMemoryIfNeeded(void) {
|
||||
size_t mem_used, mem_tofree, mem_freed;
|
||||
int slaves = listLength(server.slaves);
|
||||
mstime_t latency;
|
||||
|
||||
/* Remove the size of slaves output buffers and AOF buffer from the
|
||||
* count of used memory. */
|
||||
@@ -2781,6 +2900,7 @@ int freeMemoryIfNeeded(void) {
|
||||
/* Compute how much memory we need to free. */
|
||||
mem_tofree = mem_used - server.maxmemory;
|
||||
mem_freed = 0;
|
||||
latencyStartMonitor(latency);
|
||||
while (mem_freed < mem_tofree) {
|
||||
int j, k, keys_freed = 0;
|
||||
|
||||
@@ -2884,8 +3004,14 @@ int freeMemoryIfNeeded(void) {
|
||||
if (slaves) flushSlavesOutputBuffers();
|
||||
}
|
||||
}
|
||||
if (!keys_freed) return REDIS_ERR; /* nothing to free... */
|
||||
if (!keys_freed) {
|
||||
latencyEndMonitor(latency);
|
||||
latencyAddSampleIfNeeded("eviction-cycle",latency);
|
||||
return REDIS_ERR; /* nothing to free... */
|
||||
}
|
||||
}
|
||||
latencyEndMonitor(latency);
|
||||
latencyAddSampleIfNeeded("eviction-cycle",latency);
|
||||
return REDIS_OK;
|
||||
}
|
||||
|
||||
@@ -2939,7 +3065,7 @@ void daemonize(void) {
|
||||
}
|
||||
}
|
||||
|
||||
void version() {
|
||||
void version(void) {
|
||||
printf("Redis server v=%s sha=%s:%d malloc=%s bits=%d build=%llx\n",
|
||||
REDIS_VERSION,
|
||||
redisGitSHA1(),
|
||||
@@ -2950,7 +3076,7 @@ void version() {
|
||||
exit(0);
|
||||
}
|
||||
|
||||
void usage() {
|
||||
void usage(void) {
|
||||
fprintf(stderr,"Usage: ./redis-server [/path/to/redis.conf] [options]\n");
|
||||
fprintf(stderr," ./redis-server - (read config from stdin)\n");
|
||||
fprintf(stderr," ./redis-server -v or --version\n");
|
||||
@@ -2986,10 +3112,33 @@ void redisAsciiArt(void) {
|
||||
zfree(buf);
|
||||
}
|
||||
|
||||
static void sigtermHandler(int sig) {
|
||||
REDIS_NOTUSED(sig);
|
||||
static void sigShutdownHandler(int sig) {
|
||||
char *msg;
|
||||
|
||||
redisLogFromHandler(REDIS_WARNING,"Received SIGTERM, scheduling shutdown...");
|
||||
switch (sig) {
|
||||
case SIGINT:
|
||||
msg = "Received SIGINT scheduling shutdown...";
|
||||
break;
|
||||
case SIGTERM:
|
||||
msg = "Received SIGTERM scheduling shutdown...";
|
||||
break;
|
||||
default:
|
||||
msg = "Received shutdown signal, scheduling shutdown...";
|
||||
};
|
||||
|
||||
/* SIGINT is often delivered via Ctrl+C in an interactive session.
|
||||
* If we receive the signal the second time, we interpret this as
|
||||
* the user really wanting to quit ASAP without waiting to persist
|
||||
* on disk. */
|
||||
if (server.shutdown_asap && sig == SIGINT) {
|
||||
redisLogFromHandler(REDIS_WARNING, "You insist... exiting now.");
|
||||
rdbRemoveTempFile(getpid());
|
||||
exit(1); /* Exit with an error since this was not a clean shutdown. */
|
||||
} else if (server.loading) {
|
||||
exit(0);
|
||||
}
|
||||
|
||||
redisLogFromHandler(REDIS_WARNING, msg);
|
||||
server.shutdown_asap = 1;
|
||||
}
|
||||
|
||||
@@ -3000,8 +3149,9 @@ void setupSignalHandlers(void) {
|
||||
* Otherwise, sa_handler is used. */
|
||||
sigemptyset(&act.sa_mask);
|
||||
act.sa_flags = 0;
|
||||
act.sa_handler = sigtermHandler;
|
||||
act.sa_handler = sigShutdownHandler;
|
||||
sigaction(SIGTERM, &act, NULL);
|
||||
sigaction(SIGINT, &act, NULL);
|
||||
|
||||
#ifdef HAVE_BACKTRACE
|
||||
sigemptyset(&act.sa_mask);
|
||||
@@ -3127,6 +3277,13 @@ int main(int argc, char **argv) {
|
||||
}
|
||||
j++;
|
||||
}
|
||||
if (server.sentinel_mode && configfile && *configfile == '-') {
|
||||
redisLog(REDIS_WARNING,
|
||||
"Sentinel config from STDIN not allowed.");
|
||||
redisLog(REDIS_WARNING,
|
||||
"Sentinel needs config file on disk to save state. Exiting...");
|
||||
exit(1);
|
||||
}
|
||||
if (configfile) server.configfile = getAbsolutePath(configfile);
|
||||
resetServerSaveParams();
|
||||
loadServerConfig(configfile,options);
|
||||
|
||||
+29
-10
@@ -51,6 +51,8 @@
|
||||
#include <lua.h>
|
||||
#include <signal.h>
|
||||
|
||||
typedef long long mstime_t; /* millisecond time type. */
|
||||
|
||||
#include "ae.h" /* Event driven programming library */
|
||||
#include "sds.h" /* Dynamic safe strings */
|
||||
#include "dict.h" /* Hash tables */
|
||||
@@ -61,6 +63,8 @@
|
||||
#include "intset.h" /* Compact integer set structure */
|
||||
#include "version.h" /* Version macro */
|
||||
#include "util.h" /* Misc functions useful in many places */
|
||||
#include "latency.h" /* Latency monitor API */
|
||||
#include "sparkline.h" /* ASII graphs API */
|
||||
|
||||
/* Error codes */
|
||||
#define REDIS_OK 0
|
||||
@@ -69,7 +73,7 @@
|
||||
/* Static server configuration */
|
||||
#define REDIS_DEFAULT_HZ 10 /* Time interrupt calls/sec. */
|
||||
#define REDIS_MIN_HZ 1
|
||||
#define REDIS_MAX_HZ 500
|
||||
#define REDIS_MAX_HZ 500
|
||||
#define REDIS_SERVERPORT 6379 /* TCP port */
|
||||
#define REDIS_TCP_BACKLOG 511 /* TCP listen backlog */
|
||||
#define REDIS_MAXIDLETIME 0 /* default client timeout: infinite */
|
||||
@@ -116,6 +120,7 @@
|
||||
#define REDIS_DEFAULT_MAXMEMORY_SAMPLES 3
|
||||
#define REDIS_DEFAULT_AOF_FILENAME "appendonly.aof"
|
||||
#define REDIS_DEFAULT_AOF_NO_FSYNC_ON_REWRITE 0
|
||||
#define REDIS_DEFAULT_AOF_LOAD_TRUNCATED 1
|
||||
#define REDIS_DEFAULT_ACTIVE_REHASHING 1
|
||||
#define REDIS_DEFAULT_AOF_REWRITE_INCREMENTAL_FSYNC 1
|
||||
#define REDIS_DEFAULT_MIN_SLAVES_TO_WRITE 0
|
||||
@@ -124,6 +129,7 @@
|
||||
#define REDIS_PEER_ID_LEN (REDIS_IP_STR_LEN+32) /* Must be enough for ip:port */
|
||||
#define REDIS_BINDADDR_MAX 16
|
||||
#define REDIS_MIN_RESERVED_FDS 32
|
||||
#define REDIS_DEFAULT_LATENCY_MONITOR_THRESHOLD 0
|
||||
|
||||
#define ACTIVE_EXPIRE_CYCLE_LOOKUPS_PER_LOOP 20 /* Loopkups per loop. */
|
||||
#define ACTIVE_EXPIRE_CYCLE_FAST_DURATION 1000 /* Microseconds */
|
||||
@@ -161,6 +167,8 @@
|
||||
#define REDIS_CMD_LOADING 512 /* "l" flag */
|
||||
#define REDIS_CMD_STALE 1024 /* "t" flag */
|
||||
#define REDIS_CMD_SKIP_MONITOR 2048 /* "M" flag */
|
||||
#define REDIS_CMD_ASKING 4096 /* "k" flag */
|
||||
#define REDIS_CMD_FAST 8192 /* "F" flag */
|
||||
|
||||
/* Object types */
|
||||
#define REDIS_STRING 0
|
||||
@@ -232,6 +240,8 @@
|
||||
#define REDIS_FORCE_AOF (1<<14) /* Force AOF propagation of current cmd. */
|
||||
#define REDIS_FORCE_REPL (1<<15) /* Force replication of current cmd. */
|
||||
#define REDIS_PRE_PSYNC (1<<16) /* Instance don't understand PSYNC. */
|
||||
#define REDIS_READONLY (1<<17) /* Cluster client is in read-only state. */
|
||||
#define REDIS_PUBSUB (1<<18) /* Client is in Pub/Sub mode. */
|
||||
|
||||
/* Client request types */
|
||||
#define REDIS_REQ_INLINE 1
|
||||
@@ -375,8 +385,6 @@
|
||||
* Data types
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
typedef long long mstime_t; /* millisecond time type. */
|
||||
|
||||
/* A redis object, that is a type able to hold a string / list / set */
|
||||
|
||||
/* The actual Redis Object */
|
||||
@@ -575,6 +583,12 @@ typedef struct redisOpArray {
|
||||
* Global server state
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
/* AIX defines hz to __hz, we don't use this define and in order to allow
|
||||
* Redis build on AIX we need to undef it. */
|
||||
#ifdef _AIX
|
||||
#undef hz
|
||||
#endif
|
||||
|
||||
struct redisServer {
|
||||
/* General */
|
||||
char *configfile; /* Absolute config file path, or NULL */
|
||||
@@ -627,6 +641,7 @@ struct redisServer {
|
||||
long long stat_keyspace_misses; /* Number of failed lookups of keys */
|
||||
size_t stat_peak_memory; /* Max used memory record */
|
||||
long long stat_fork_time; /* Time needed to perform latest fork() */
|
||||
double stat_fork_rate; /* Fork rate in GB/sec. */
|
||||
long long stat_rejected_conn; /* Clients rejected because of maxclients */
|
||||
long long stat_sync_full; /* Number of full resyncs with slaves. */
|
||||
long long stat_sync_partial_ok; /* Number of accepted PSYNC requests. */
|
||||
@@ -675,6 +690,7 @@ struct redisServer {
|
||||
int aof_rewrite_incremental_fsync;/* fsync incrementally while rewriting? */
|
||||
int aof_last_write_status; /* REDIS_OK or REDIS_ERR */
|
||||
int aof_last_write_errno; /* Valid if aof_last_write_status is ERR */
|
||||
int aof_load_truncated; /* Don't stop on unexpected AOF EOF. */
|
||||
/* RDB persistence */
|
||||
long long dirty; /* Changes to DB from the last save */
|
||||
long long dirty_before_bgsave; /* Used to restore dirty on failed BGSAVE */
|
||||
@@ -740,9 +756,9 @@ struct redisServer {
|
||||
/* Replication script cache. */
|
||||
dict *repl_scriptcache_dict; /* SHA1 all slaves are aware of. */
|
||||
list *repl_scriptcache_fifo; /* First in, first out LRU eviction. */
|
||||
int repl_scriptcache_size; /* Max number of elements. */
|
||||
unsigned int repl_scriptcache_size; /* Max number of elements. */
|
||||
/* Limits */
|
||||
int maxclients; /* Max number of simultaneous clients */
|
||||
unsigned int maxclients; /* Max number of simultaneous clients */
|
||||
unsigned long long maxmemory; /* Max number of memory bytes to use */
|
||||
int maxmemory_policy; /* Policy for key eviction */
|
||||
int maxmemory_samples; /* Pricision of random sampling */
|
||||
@@ -786,6 +802,9 @@ struct redisServer {
|
||||
int lua_timedout; /* True if we reached the time limit for script
|
||||
execution. */
|
||||
int lua_kill; /* Kill the script if true. */
|
||||
/* Latency monitor */
|
||||
long long latency_monitor_threshold;
|
||||
dict *latency_events;
|
||||
/* Assert & bug reporting */
|
||||
char *assert_failed;
|
||||
char *assert_file;
|
||||
@@ -909,11 +928,10 @@ void freeClient(redisClient *c);
|
||||
void freeClientAsync(redisClient *c);
|
||||
void resetClient(redisClient *c);
|
||||
void sendReplyToClient(aeEventLoop *el, int fd, void *privdata, int mask);
|
||||
void addReply(redisClient *c, robj *obj);
|
||||
void *addDeferredMultiBulkLength(redisClient *c);
|
||||
void setDeferredMultiBulkLength(redisClient *c, void *node, long length);
|
||||
void addReplySds(redisClient *c, sds s);
|
||||
void processInputBuffer(redisClient *c);
|
||||
void acceptHandler(aeEventLoop *el, int fd, void *privdata, int mask);
|
||||
void acceptTcpHandler(aeEventLoop *el, int fd, void *privdata, int mask);
|
||||
void acceptUnixHandler(aeEventLoop *el, int fd, void *privdata, int mask);
|
||||
void readQueryFromClient(aeEventLoop *el, int fd, void *privdata, int mask);
|
||||
@@ -921,7 +939,6 @@ void addReplyBulk(redisClient *c, robj *obj);
|
||||
void addReplyBulkCString(redisClient *c, char *s);
|
||||
void addReplyBulkCBuffer(redisClient *c, void *p, size_t len);
|
||||
void addReplyBulkLongLong(redisClient *c, long long ll);
|
||||
void acceptHandler(aeEventLoop *el, int fd, void *privdata, int mask);
|
||||
void addReply(redisClient *c, robj *obj);
|
||||
void addReplySds(redisClient *c, sds s);
|
||||
void addReplyError(redisClient *c, char *err);
|
||||
@@ -1114,7 +1131,7 @@ void redisLog(int level, const char *fmt, ...);
|
||||
#endif
|
||||
void redisLogRaw(int level, const char *msg);
|
||||
void redisLogFromHandler(int level, const char *msg);
|
||||
void usage();
|
||||
void usage(void);
|
||||
void updateDictResizePolicy(void);
|
||||
int htNeedsResize(dict *dict);
|
||||
void oom(const char *msg);
|
||||
@@ -1173,7 +1190,7 @@ sds keyspaceEventsFlagsToString(int flags);
|
||||
/* Configuration */
|
||||
void loadServerConfig(char *filename, char *options);
|
||||
void appendServerSaveParams(time_t seconds, int changes);
|
||||
void resetServerSaveParams();
|
||||
void resetServerSaveParams(void);
|
||||
struct rewriteConfigState; /* Forward declaration to export API. */
|
||||
void rewriteConfigRewriteLine(struct rewriteConfigState *state, char *option, sds line, int force);
|
||||
int rewriteConfig(char *path);
|
||||
@@ -1232,6 +1249,7 @@ uint64_t redisBuildId(void);
|
||||
void authCommand(redisClient *c);
|
||||
void pingCommand(redisClient *c);
|
||||
void echoCommand(redisClient *c);
|
||||
void commandCommand(redisClient *c);
|
||||
void setCommand(redisClient *c);
|
||||
void setnxCommand(redisClient *c);
|
||||
void setexCommand(redisClient *c);
|
||||
@@ -1380,6 +1398,7 @@ void pfaddCommand(redisClient *c);
|
||||
void pfcountCommand(redisClient *c);
|
||||
void pfmergeCommand(redisClient *c);
|
||||
void pfdebugCommand(redisClient *c);
|
||||
void latencyCommand(redisClient *c);
|
||||
|
||||
#if defined(__GNUC__)
|
||||
void *calloc(size_t count, size_t size) __attribute__ ((deprecated));
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/* Every time the Redis Git SHA1 or Dirty status changes only this small
|
||||
/* Every time the Redis Git SHA1 or Dirty status changes only this small
|
||||
* file is recompiled, as we access this information in all the other
|
||||
* files using this functions. */
|
||||
|
||||
|
||||
+2
-2
@@ -198,7 +198,7 @@ void replicationFeedSlaves(list *slaves, int dictid, robj **argv, int argc) {
|
||||
long objlen = stringObjectLen(argv[j]);
|
||||
|
||||
/* We need to feed the buffer with the object as a bulk reply
|
||||
* not just as a plain string, so create the $..CRLF payload len
|
||||
* not just as a plain string, so create the $..CRLF payload len
|
||||
* ad add the final CRLF */
|
||||
aux[0] = '$';
|
||||
len = ll2string(aux+1,sizeof(aux)-1,objlen);
|
||||
@@ -1620,7 +1620,7 @@ void replicationCron(void) {
|
||||
if (server.masterhost && server.master &&
|
||||
!(server.master->flags & REDIS_PRE_PSYNC))
|
||||
replicationSendAck();
|
||||
|
||||
|
||||
/* If we have attached slaves, PING them from time to time.
|
||||
* So slaves can implement an explicit timeout to masters, and will
|
||||
* be able to detect a link disconnection even if the TCP connection
|
||||
|
||||
+6
-4
@@ -212,7 +212,7 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
static robj **argv = NULL;
|
||||
static int argv_size = 0;
|
||||
static robj *cached_objects[LUA_CMD_OBJCACHE_SIZE];
|
||||
static int cached_objects_len[LUA_CMD_OBJCACHE_SIZE];
|
||||
static size_t cached_objects_len[LUA_CMD_OBJCACHE_SIZE];
|
||||
|
||||
/* Require at least one argument */
|
||||
if (argc == 0) {
|
||||
@@ -222,9 +222,7 @@ int luaRedisGenericCommand(lua_State *lua, int raise_error) {
|
||||
}
|
||||
|
||||
/* Build the arguments vector */
|
||||
if (!argv) {
|
||||
argv = zmalloc(sizeof(robj*)*argc);
|
||||
} else if (argv_size < argc) {
|
||||
if (argv_size < argc) {
|
||||
argv = zrealloc(argv,sizeof(robj*)*argc);
|
||||
argv_size = argc;
|
||||
}
|
||||
@@ -401,6 +399,7 @@ cleanup:
|
||||
if (c->argv != argv) {
|
||||
zfree(c->argv);
|
||||
argv = NULL;
|
||||
argv_size = 0;
|
||||
}
|
||||
|
||||
if (raise_error) {
|
||||
@@ -909,6 +908,9 @@ void evalGenericCommand(redisClient *c, int evalsha) {
|
||||
if (numkeys > (c->argc - 3)) {
|
||||
addReplyError(c,"Number of keys can't be greater than number of args");
|
||||
return;
|
||||
} else if (numkeys < 0) {
|
||||
addReplyError(c,"Number of keys can't be negative");
|
||||
return;
|
||||
}
|
||||
|
||||
/* We obtain the script SHA1, then check if this function is already
|
||||
|
||||
@@ -123,7 +123,7 @@ void sdsclear(sds s) {
|
||||
/* Enlarge the free space at the end of the sds string so that the caller
|
||||
* is sure that after calling this function can overwrite up to addlen
|
||||
* bytes after the end of the string, plus one more byte for nul term.
|
||||
*
|
||||
*
|
||||
* Note: this does not change the *length* of the sds string as returned
|
||||
* by sdslen(), but only the free buffer space we have. */
|
||||
sds sdsMakeRoomFor(sds s, size_t addlen) {
|
||||
@@ -200,10 +200,12 @@ size_t sdsAllocSize(sds s) {
|
||||
void sdsIncrLen(sds s, int incr) {
|
||||
struct sdshdr *sh = (void*) (s-(sizeof(struct sdshdr)));
|
||||
|
||||
assert(sh->free >= incr);
|
||||
if (incr >= 0)
|
||||
assert(sh->free >= (unsigned int)incr);
|
||||
else
|
||||
assert(sh->len >= (unsigned int)(-incr));
|
||||
sh->len += incr;
|
||||
sh->free -= incr;
|
||||
assert(sh->free >= 0);
|
||||
s[sh->len] = '\0';
|
||||
}
|
||||
|
||||
@@ -388,6 +390,7 @@ sds sdscatvprintf(sds s, const char *fmt, va_list ap) {
|
||||
buf[buflen-2] = '\0';
|
||||
va_copy(cpy,ap);
|
||||
vsnprintf(buf, buflen, fmt, cpy);
|
||||
va_end(ap);
|
||||
if (buf[buflen-2] != '\0') {
|
||||
if (buf != staticbuf) zfree(buf);
|
||||
buflen *= 2;
|
||||
@@ -457,7 +460,7 @@ sds sdscatfmt(sds s, char const *fmt, ...) {
|
||||
i = initlen; /* Position of the next byte to write to dest str. */
|
||||
while(*f) {
|
||||
char next, *str;
|
||||
int l;
|
||||
unsigned int l;
|
||||
long long num;
|
||||
unsigned long long unum;
|
||||
|
||||
|
||||
@@ -39,8 +39,8 @@
|
||||
typedef char *sds;
|
||||
|
||||
struct sdshdr {
|
||||
int len;
|
||||
int free;
|
||||
unsigned int len;
|
||||
unsigned int free;
|
||||
char buf[];
|
||||
};
|
||||
|
||||
|
||||
+66
-18
@@ -159,7 +159,7 @@ typedef struct sentinelRedisInstance {
|
||||
/* Master specific. */
|
||||
dict *sentinels; /* Other sentinels monitoring the same master. */
|
||||
dict *slaves; /* Slaves for this master instance. */
|
||||
int quorum; /* Number of sentinels that need to agree on failure. */
|
||||
unsigned int quorum;/* Number of sentinels that need to agree on failure. */
|
||||
int parallel_syncs; /* How many slaves to reconfigure at same time. */
|
||||
char *auth_pass; /* Password to use for AUTH against master & slaves. */
|
||||
|
||||
@@ -203,6 +203,10 @@ struct sentinelState {
|
||||
mstime_t tilt_start_time; /* When TITL started. */
|
||||
mstime_t previous_time; /* Last time we ran the time handler. */
|
||||
list *scripts_queue; /* Queue of user scripts to execute. */
|
||||
char *announce_ip; /* IP addr that is gossiped to other sentinels if
|
||||
not NULL. */
|
||||
int announce_port; /* Port that is gossiped to other sentinels if
|
||||
non zero. */
|
||||
} sentinel;
|
||||
|
||||
/* A script execution job. */
|
||||
@@ -345,6 +349,7 @@ int dictSdsKeyCompare(void *privdata, const void *key1, const void *key2);
|
||||
void releaseSentinelRedisInstance(sentinelRedisInstance *ri);
|
||||
|
||||
void dictInstancesValDestructor (void *privdata, void *obj) {
|
||||
REDIS_NOTUSED(privdata);
|
||||
releaseSentinelRedisInstance(obj);
|
||||
}
|
||||
|
||||
@@ -403,7 +408,7 @@ void initSentinelConfig(void) {
|
||||
|
||||
/* Perform the Sentinel mode initialization. */
|
||||
void initSentinel(void) {
|
||||
int j;
|
||||
unsigned int j;
|
||||
|
||||
/* Remove usual Redis commands from the command table, then just add
|
||||
* the SENTINEL command. */
|
||||
@@ -424,6 +429,8 @@ void initSentinel(void) {
|
||||
sentinel.previous_time = mstime();
|
||||
sentinel.running_scripts = 0;
|
||||
sentinel.scripts_queue = listCreate();
|
||||
sentinel.announce_ip = NULL;
|
||||
sentinel.announce_port = 0;
|
||||
}
|
||||
|
||||
/* This function gets called when the server is in Sentinel mode, started,
|
||||
@@ -455,19 +462,19 @@ void sentinelIsRunning(void) {
|
||||
* EINVAL: Invalid port number.
|
||||
*/
|
||||
sentinelAddr *createSentinelAddr(char *hostname, int port) {
|
||||
char buf[32];
|
||||
char ip[REDIS_IP_STR_LEN];
|
||||
sentinelAddr *sa;
|
||||
|
||||
if (port <= 0 || port > 65535) {
|
||||
errno = EINVAL;
|
||||
return NULL;
|
||||
}
|
||||
if (anetResolve(NULL,hostname,buf,sizeof(buf)) == ANET_ERR) {
|
||||
if (anetResolve(NULL,hostname,ip,sizeof(ip)) == ANET_ERR) {
|
||||
errno = ENOENT;
|
||||
return NULL;
|
||||
}
|
||||
sa = zmalloc(sizeof(*sa));
|
||||
sa->ip = sdsnew(buf);
|
||||
sa->ip = sdsnew(ip);
|
||||
sa->port = port;
|
||||
return sa;
|
||||
}
|
||||
@@ -1424,6 +1431,13 @@ char *sentinelHandleConfiguration(char **argv, int argc) {
|
||||
return "Wrong hostname or port for sentinel.";
|
||||
}
|
||||
if (argc == 5) si->runid = sdsnew(argv[4]);
|
||||
} else if (!strcasecmp(argv[0],"announce-ip") && argc == 2) {
|
||||
/* announce-ip <ip-address> */
|
||||
if (strlen(argv[1]))
|
||||
sentinel.announce_ip = sdsnew(argv[1]);
|
||||
} else if (!strcasecmp(argv[0],"announce-port") && argc == 2) {
|
||||
/* announce-port <port> */
|
||||
sentinel.announce_port = atoi(argv[1]);
|
||||
} else {
|
||||
return "Unrecognized sentinel configuration statement.";
|
||||
}
|
||||
@@ -1555,6 +1569,20 @@ void rewriteConfigSentinelOption(struct rewriteConfigState *state) {
|
||||
"sentinel current-epoch %llu", (unsigned long long) sentinel.current_epoch);
|
||||
rewriteConfigRewriteLine(state,"sentinel",line,1);
|
||||
|
||||
/* sentinel announce-ip. */
|
||||
if (sentinel.announce_ip) {
|
||||
line = sdsnew("sentinel announce-ip ");
|
||||
line = sdscatrepr(line, sentinel.announce_ip, sdslen(sentinel.announce_ip));
|
||||
rewriteConfigRewriteLine(state,"sentinel",line,1);
|
||||
}
|
||||
|
||||
/* sentinel announce-port. */
|
||||
if (sentinel.announce_port) {
|
||||
line = sdscatprintf(sdsempty(),"sentinel announce-port %d",
|
||||
sentinel.announce_port);
|
||||
rewriteConfigRewriteLine(state,"sentinel",line,1);
|
||||
}
|
||||
|
||||
dictReleaseIterator(di);
|
||||
}
|
||||
|
||||
@@ -1634,6 +1662,7 @@ void sentinelLinkEstablishedCallback(const redisAsyncContext *c, int status) {
|
||||
}
|
||||
|
||||
void sentinelDisconnectCallback(const redisAsyncContext *c, int status) {
|
||||
REDIS_NOTUSED(status);
|
||||
sentinelDisconnectInstanceFromContext(c);
|
||||
}
|
||||
|
||||
@@ -1998,6 +2027,7 @@ void sentinelRefreshInstanceInfo(sentinelRedisInstance *ri, const char *info) {
|
||||
void sentinelInfoReplyCallback(redisAsyncContext *c, void *reply, void *privdata) {
|
||||
sentinelRedisInstance *ri = c->data;
|
||||
redisReply *r;
|
||||
REDIS_NOTUSED(privdata);
|
||||
|
||||
if (ri) ri->pending_commands--;
|
||||
if (!reply || !ri) return;
|
||||
@@ -2012,6 +2042,8 @@ void sentinelInfoReplyCallback(redisAsyncContext *c, void *reply, void *privdata
|
||||
* value of the command but its effects directly. */
|
||||
void sentinelDiscardReplyCallback(redisAsyncContext *c, void *reply, void *privdata) {
|
||||
sentinelRedisInstance *ri = c->data;
|
||||
REDIS_NOTUSED(reply);
|
||||
REDIS_NOTUSED(privdata);
|
||||
|
||||
if (ri) ri->pending_commands--;
|
||||
}
|
||||
@@ -2019,6 +2051,7 @@ void sentinelDiscardReplyCallback(redisAsyncContext *c, void *reply, void *privd
|
||||
void sentinelPingReplyCallback(redisAsyncContext *c, void *reply, void *privdata) {
|
||||
sentinelRedisInstance *ri = c->data;
|
||||
redisReply *r;
|
||||
REDIS_NOTUSED(privdata);
|
||||
|
||||
if (ri) ri->pending_commands--;
|
||||
if (!reply || !ri) return;
|
||||
@@ -2057,6 +2090,7 @@ void sentinelPingReplyCallback(redisAsyncContext *c, void *reply, void *privdata
|
||||
void sentinelPublishReplyCallback(redisAsyncContext *c, void *reply, void *privdata) {
|
||||
sentinelRedisInstance *ri = c->data;
|
||||
redisReply *r;
|
||||
REDIS_NOTUSED(privdata);
|
||||
|
||||
if (ri) ri->pending_commands--;
|
||||
if (!reply || !ri) return;
|
||||
@@ -2166,6 +2200,7 @@ cleanup:
|
||||
void sentinelReceiveHelloMessages(redisAsyncContext *c, void *reply, void *privdata) {
|
||||
sentinelRedisInstance *ri = c->data;
|
||||
redisReply *r;
|
||||
REDIS_NOTUSED(privdata);
|
||||
|
||||
if (!reply || !ri) return;
|
||||
r = reply;
|
||||
@@ -2205,18 +2240,30 @@ int sentinelSendHello(sentinelRedisInstance *ri) {
|
||||
char ip[REDIS_IP_STR_LEN];
|
||||
char payload[REDIS_IP_STR_LEN+1024];
|
||||
int retval;
|
||||
char *announce_ip;
|
||||
int announce_port;
|
||||
sentinelRedisInstance *master = (ri->flags & SRI_MASTER) ? ri : ri->master;
|
||||
sentinelAddr *master_addr = sentinelGetCurrentMasterAddress(master);
|
||||
|
||||
/* Try to obtain our own IP address. */
|
||||
if (anetSockName(ri->cc->c.fd,ip,sizeof(ip),NULL) == -1) return REDIS_ERR;
|
||||
if (ri->flags & SRI_DISCONNECTED) return REDIS_ERR;
|
||||
|
||||
/* Use the specified announce address if specified, otherwise try to
|
||||
* obtain our own IP address. */
|
||||
if (sentinel.announce_ip) {
|
||||
announce_ip = sentinel.announce_ip;
|
||||
} else {
|
||||
if (anetSockName(ri->cc->c.fd,ip,sizeof(ip),NULL) == -1)
|
||||
return REDIS_ERR;
|
||||
announce_ip = ip;
|
||||
}
|
||||
announce_port = sentinel.announce_port ?
|
||||
sentinel.announce_port : server.port;
|
||||
|
||||
/* Format and send the Hello message. */
|
||||
snprintf(payload,sizeof(payload),
|
||||
"%s,%d,%s,%llu," /* Info about this sentinel. */
|
||||
"%s,%s,%d,%llu", /* Info about current master. */
|
||||
ip, server.port, server.runid,
|
||||
announce_ip, announce_port, server.runid,
|
||||
(unsigned long long) sentinel.current_epoch,
|
||||
/* --- */
|
||||
master->name,master_addr->ip,master_addr->port,
|
||||
@@ -2559,7 +2606,7 @@ sentinelRedisInstance *sentinelGetMasterByNameOrReplyError(redisClient *c,
|
||||
{
|
||||
sentinelRedisInstance *ri;
|
||||
|
||||
ri = dictFetchValue(sentinel.masters,c->argv[2]->ptr);
|
||||
ri = dictFetchValue(sentinel.masters,name->ptr);
|
||||
if (!ri) {
|
||||
addReplyError(c,"No such master with that name");
|
||||
return NULL;
|
||||
@@ -2682,7 +2729,7 @@ void sentinelCommand(redisClient *c) {
|
||||
/* SENTINEL MONITOR <name> <ip> <port> <quorum> */
|
||||
sentinelRedisInstance *ri;
|
||||
long quorum, port;
|
||||
char buf[32];
|
||||
char ip[REDIS_IP_STR_LEN];
|
||||
|
||||
if (c->argc != 6) goto numargserr;
|
||||
if (getLongFromObjectOrReply(c,c->argv[5],&quorum,"Invalid quorum")
|
||||
@@ -2692,7 +2739,7 @@ void sentinelCommand(redisClient *c) {
|
||||
/* Make sure the IP field is actually a valid IP before passing it
|
||||
* to createSentinelRedisInstance(), otherwise we may trigger a
|
||||
* DNS lookup at runtime. */
|
||||
if (anetResolveIP(NULL,c->argv[3]->ptr,buf,sizeof(buf)) == ANET_ERR) {
|
||||
if (anetResolveIP(NULL,c->argv[3]->ptr,ip,sizeof(ip)) == ANET_ERR) {
|
||||
addReplyError(c,"Invalid IP address specified");
|
||||
return;
|
||||
}
|
||||
@@ -2997,7 +3044,7 @@ void sentinelCheckSubjectivelyDown(sentinelRedisInstance *ri) {
|
||||
void sentinelCheckObjectivelyDown(sentinelRedisInstance *master) {
|
||||
dictIterator *di;
|
||||
dictEntry *de;
|
||||
int quorum = 0, odown = 0;
|
||||
unsigned int quorum = 0, odown = 0;
|
||||
|
||||
if (master->flags & SRI_S_DOWN) {
|
||||
/* Is down for enough sentinels? */
|
||||
@@ -3034,6 +3081,7 @@ void sentinelCheckObjectivelyDown(sentinelRedisInstance *master) {
|
||||
void sentinelReceiveIsMasterDownReply(redisAsyncContext *c, void *reply, void *privdata) {
|
||||
sentinelRedisInstance *ri = c->data;
|
||||
redisReply *r;
|
||||
REDIS_NOTUSED(privdata);
|
||||
|
||||
if (ri) ri->pending_commands--;
|
||||
if (!reply || !ri) return;
|
||||
@@ -3057,7 +3105,7 @@ void sentinelReceiveIsMasterDownReply(redisAsyncContext *c, void *reply, void *p
|
||||
/* If the runid in the reply is not "*" the Sentinel actually
|
||||
* replied with a vote. */
|
||||
sdsfree(ri->leader);
|
||||
if (ri->leader_epoch != r->element[2]->integer)
|
||||
if ((long long)ri->leader_epoch != r->element[2]->integer)
|
||||
redisLog(REDIS_WARNING,
|
||||
"%s voted for %s %llu", ri->name,
|
||||
r->element[1]->str,
|
||||
@@ -3176,9 +3224,9 @@ int sentinelLeaderIncr(dict *counters, char *runid) {
|
||||
/* Scan all the Sentinels attached to this master to check if there
|
||||
* is a leader for the specified epoch.
|
||||
*
|
||||
* To be a leader for a given epoch, we should have the majorify of
|
||||
* the Sentinels we know that reported the same instance as
|
||||
* leader for the same epoch. */
|
||||
* To be a leader for a given epoch, we should have the majority of
|
||||
* the Sentinels we know (ever seen since the last SENTINEL RESET) that
|
||||
* reported the same instance as leader for the same epoch. */
|
||||
char *sentinelGetLeader(sentinelRedisInstance *master, uint64_t epoch) {
|
||||
dict *counters;
|
||||
dictIterator *di;
|
||||
@@ -3192,13 +3240,14 @@ char *sentinelGetLeader(sentinelRedisInstance *master, uint64_t epoch) {
|
||||
redisAssert(master->flags & (SRI_O_DOWN|SRI_FAILOVER_IN_PROGRESS));
|
||||
counters = dictCreate(&leaderVotesDictType,NULL);
|
||||
|
||||
voters = dictSize(master->sentinels)+1; /* All the other sentinels and me. */
|
||||
|
||||
/* Count other sentinels votes */
|
||||
di = dictGetIterator(master->sentinels);
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
sentinelRedisInstance *ri = dictGetVal(de);
|
||||
if (ri->leader != NULL && ri->leader_epoch == sentinel.current_epoch)
|
||||
sentinelLeaderIncr(counters,ri->leader);
|
||||
voters++;
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
|
||||
@@ -3232,7 +3281,6 @@ char *sentinelGetLeader(sentinelRedisInstance *master, uint64_t epoch) {
|
||||
winner = myvote;
|
||||
}
|
||||
}
|
||||
voters++; /* Anyway, count me as one of the voters. */
|
||||
|
||||
voters_quorum = voters/2+1;
|
||||
if (winner && (max_votes < voters_quorum || max_votes < master->quorum))
|
||||
|
||||
+175
@@ -0,0 +1,175 @@
|
||||
/* sparkline.c -- ASCII Sparklines
|
||||
* This code is modified from http://github.com/antirez/aspark and adapted
|
||||
* in order to return SDS strings instead of outputting directly to
|
||||
* the terminal.
|
||||
*
|
||||
* ---------------------------------------------------------------------------
|
||||
*
|
||||
* Copyright(C) 2011-2014 Salvatore Sanfilippo <antirez@gmail.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.
|
||||
*
|
||||
* 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 "redis.h"
|
||||
|
||||
#include <math.h>
|
||||
|
||||
/* This is the charset used to display the graphs, but multiple rows are used
|
||||
* to increase the resolution. */
|
||||
static char charset[] = "_-`";
|
||||
static char charset_fill[] = "_o#";
|
||||
static int charset_len = sizeof(charset)-1;
|
||||
static int label_margin_top = 1;
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* Sequences are arrays of samples we use to represent data to turn
|
||||
* into sparklines. This is the API in order to generate a sparkline:
|
||||
*
|
||||
* struct sequence *seq = createSparklineSequence();
|
||||
* sparklineSequenceAddSample(seq, 10, NULL);
|
||||
* sparklineSequenceAddSample(seq, 20, NULL);
|
||||
* sparklineSequenceAddSample(seq, 30, "last sample label");
|
||||
* sds output = sparklineRender(seq, 80, 4);
|
||||
* freeSparklineSequence(seq);
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/* Create a new sequence. */
|
||||
struct sequence *createSparklineSequence(void) {
|
||||
struct sequence *seq = zmalloc(sizeof(*seq));
|
||||
seq->length = 0;
|
||||
seq->samples = NULL;
|
||||
return seq;
|
||||
}
|
||||
|
||||
/* Add a new sample into a sequence. */
|
||||
void sparklineSequenceAddSample(struct sequence *seq, double value, char *label) {
|
||||
if (seq->length == 0) {
|
||||
seq->min = seq->max = value;
|
||||
} else {
|
||||
if (value < seq->min) seq->min = value;
|
||||
else if (value > seq->max) seq->max = value;
|
||||
}
|
||||
seq->samples = zrealloc(seq->samples,sizeof(struct sample)*(seq->length+1));
|
||||
seq->samples[seq->length].value = value;
|
||||
seq->samples[seq->length].label = label;
|
||||
seq->length++;
|
||||
if (label) seq->labels++;
|
||||
}
|
||||
|
||||
/* Free a sequence. */
|
||||
void freeSparklineSequence(struct sequence *seq) {
|
||||
int j;
|
||||
|
||||
for (j = 0; j < seq->length; j++)
|
||||
zfree(seq->samples[j].label);
|
||||
zfree(seq->samples);
|
||||
zfree(seq);
|
||||
}
|
||||
|
||||
/* ----------------------------------------------------------------------------
|
||||
* ASCII rendering of sequence
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/* Render part of a sequence, so that render_sequence() call call this function
|
||||
* with differnent parts in order to create the full output without overflowing
|
||||
* the current terminal columns. */
|
||||
sds sparklineRenderRange(sds output, struct sequence *seq, int rows, int offset, int len, int flags) {
|
||||
int j;
|
||||
double relmax = seq->max - seq->min;
|
||||
int steps = charset_len*rows;
|
||||
int row = 0;
|
||||
char *chars = zmalloc(len);
|
||||
int loop = 1;
|
||||
int opt_fill = flags & SPARKLINE_FILL;
|
||||
int opt_log = flags & SPARKLINE_LOG_SCALE;
|
||||
|
||||
if (opt_log) {
|
||||
relmax = log(relmax+1);
|
||||
} else if (relmax == 0) {
|
||||
relmax = 1;
|
||||
}
|
||||
|
||||
while(loop) {
|
||||
loop = 0;
|
||||
memset(chars,' ',len);
|
||||
for (j = 0; j < len; j++) {
|
||||
struct sample *s = &seq->samples[j+offset];
|
||||
double relval = s->value - seq->min;
|
||||
int step;
|
||||
|
||||
if (opt_log) relval = log(relval+1);
|
||||
step = (int) (relval*steps)/relmax;
|
||||
if (step < 0) step = 0;
|
||||
if (step >= steps) step = steps-1;
|
||||
|
||||
if (row < rows) {
|
||||
/* Print the character needed to create the sparkline */
|
||||
int charidx = step-((rows-row-1)*charset_len);
|
||||
loop = 1;
|
||||
if (charidx >= 0 && charidx < charset_len) {
|
||||
chars[j] = opt_fill ? charset_fill[charidx] :
|
||||
charset[charidx];
|
||||
} else if(opt_fill && charidx >= charset_len) {
|
||||
chars[j] = '|';
|
||||
}
|
||||
} else {
|
||||
/* Labels spacing */
|
||||
if (seq->labels && row-rows < label_margin_top) {
|
||||
loop = 1;
|
||||
break;
|
||||
}
|
||||
/* Print the label if needed. */
|
||||
if (s->label) {
|
||||
int label_len = strlen(s->label);
|
||||
int label_char = row - rows - label_margin_top;
|
||||
|
||||
if (label_len > label_char) {
|
||||
loop = 1;
|
||||
chars[j] = s->label[label_char];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (loop) {
|
||||
row++;
|
||||
output = sdscatlen(output,chars,len);
|
||||
output = sdscatlen(output,"\n",1);
|
||||
}
|
||||
}
|
||||
zfree(chars);
|
||||
return output;
|
||||
}
|
||||
|
||||
/* Turn a sequence into its ASCII representation */
|
||||
sds sparklineRender(sds output, struct sequence *seq, int columns, int rows, int flags) {
|
||||
int j;
|
||||
|
||||
for (j = 0; j < seq->length; j += columns) {
|
||||
int sublen = (seq->length-j) < columns ? (seq->length-j) : columns;
|
||||
|
||||
if (j != 0) output = sdscatlen(output,"\n",1);
|
||||
output = sparklineRenderRange(output, seq, rows, j, sublen, flags);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
/* sparkline.h -- ASCII Sparklines header file
|
||||
*
|
||||
* ---------------------------------------------------------------------------
|
||||
*
|
||||
* Copyright(C) 2011-2014 Salvatore Sanfilippo <antirez@gmail.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.
|
||||
*
|
||||
* 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 __SPARKLINE_H
|
||||
#define __SPARKLINE_H
|
||||
|
||||
/* A sequence is represented of many "samples" */
|
||||
struct sample {
|
||||
double value;
|
||||
char *label;
|
||||
};
|
||||
|
||||
struct sequence {
|
||||
int length;
|
||||
int labels;
|
||||
struct sample *samples;
|
||||
double min, max;
|
||||
};
|
||||
|
||||
#define SPARKLINE_NO_FLAGS 0
|
||||
#define SPARKLINE_FILL 1 /* Fill the area under the curve. */
|
||||
#define SPARKLINE_LOG_SCALE 2 /* Use logarithmic scale. */
|
||||
|
||||
struct sequence *createSparklineSequence(void);
|
||||
void sparklineSequenceAddSample(struct sequence *seq, double value, char *label);
|
||||
void freeSparklineSequence(struct sequence *seq);
|
||||
sds sparklineRenderRange(sds output, struct sequence *seq, int rows, int offset, int len, int flags);
|
||||
sds sparklineRender(sds output, struct sequence *seq, int columns, int rows, int flags);
|
||||
|
||||
#endif /* __SPARKLINE_H */
|
||||
+1
-1
@@ -119,7 +119,7 @@ ssize_t syncRead(int fd, char *ptr, ssize_t size, long long timeout) {
|
||||
|
||||
/* Read a line making sure that every char will not require more than 'timeout'
|
||||
* milliseconds to be read.
|
||||
*
|
||||
*
|
||||
* On success the number of bytes read is returned, otherwise -1.
|
||||
* On success the string is always correctly terminated with a 0 byte. */
|
||||
ssize_t syncReadLine(int fd, char *ptr, ssize_t size, long long timeout) {
|
||||
|
||||
+1
-1
@@ -1015,7 +1015,7 @@ void handleClientsBlockedOnLists(void) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (listTypeLength(o) == 0) dbDelete(rl->db,rl->key);
|
||||
/* We don't call signalModifiedKey() as it was already called
|
||||
* when an element was pushed on the list. */
|
||||
|
||||
+1
-1
@@ -515,7 +515,7 @@ void srandmemberWithCountCommand(redisClient *c) {
|
||||
size--;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* CASE 4: We have a big set compared to the requested number of elements.
|
||||
* In this case we can simply get random elements from the set and add
|
||||
* to the temporary set, trying to eventually get enough unique elements
|
||||
|
||||
+6
-6
@@ -69,7 +69,7 @@ void setGenericCommand(redisClient *c, int flags, robj *key, robj *val, robj *ex
|
||||
if (getLongLongFromObjectOrReply(c, expire, &milliseconds, NULL) != REDIS_OK)
|
||||
return;
|
||||
if (milliseconds <= 0) {
|
||||
addReplyError(c,"invalid expire time in SETEX");
|
||||
addReplyErrorFormat(c,"invalid expire time in %s",c->cmd->name);
|
||||
return;
|
||||
}
|
||||
if (unit == UNIT_SECONDS) milliseconds *= 1000;
|
||||
@@ -231,13 +231,13 @@ void setrangeCommand(redisClient *c) {
|
||||
|
||||
void getrangeCommand(redisClient *c) {
|
||||
robj *o;
|
||||
long start, end;
|
||||
long long start, end;
|
||||
char *str, llbuf[32];
|
||||
size_t strlen;
|
||||
|
||||
if (getLongFromObjectOrReply(c,c->argv[2],&start,NULL) != REDIS_OK)
|
||||
if (getLongLongFromObjectOrReply(c,c->argv[2],&start,NULL) != REDIS_OK)
|
||||
return;
|
||||
if (getLongFromObjectOrReply(c,c->argv[3],&end,NULL) != REDIS_OK)
|
||||
if (getLongLongFromObjectOrReply(c,c->argv[3],&end,NULL) != REDIS_OK)
|
||||
return;
|
||||
if ((o = lookupKeyReadOrReply(c,c->argv[1],shared.emptybulk)) == NULL ||
|
||||
checkType(c,o,REDIS_STRING)) return;
|
||||
@@ -255,11 +255,11 @@ void getrangeCommand(redisClient *c) {
|
||||
if (end < 0) end = strlen+end;
|
||||
if (start < 0) start = 0;
|
||||
if (end < 0) end = 0;
|
||||
if ((unsigned)end >= strlen) end = strlen-1;
|
||||
if ((unsigned long long)end >= strlen) end = strlen-1;
|
||||
|
||||
/* Precondition: end >= 0 && end < strlen, so the only condition where
|
||||
* nothing can be returned is: start > end. */
|
||||
if (start > end) {
|
||||
if (start > end || strlen == 0) {
|
||||
addReply(c,shared.emptybulk);
|
||||
} else {
|
||||
addReplyBulkCBuffer(c,(char*)str+start,end-start+1);
|
||||
|
||||
+60
-35
@@ -205,8 +205,6 @@ int zslDelete(zskiplist *zsl, double score, robj *obj) {
|
||||
zslDeleteNode(zsl, x, update);
|
||||
zslFreeNode(x);
|
||||
return 1;
|
||||
} else {
|
||||
return 0; /* not found */
|
||||
}
|
||||
return 0; /* not found */
|
||||
}
|
||||
@@ -1169,7 +1167,7 @@ void zsetConvert(robj *zobj, int encoding) {
|
||||
}
|
||||
|
||||
/*-----------------------------------------------------------------------------
|
||||
* Sorted set commands
|
||||
* Sorted set commands
|
||||
*----------------------------------------------------------------------------*/
|
||||
|
||||
/* This generic command implements both ZADD and ZINCRBY. */
|
||||
@@ -1980,50 +1978,77 @@ void zunionInterGenericCommand(redisClient *c, robj *dstkey, int op) {
|
||||
zuiClearIterator(&src[0]);
|
||||
}
|
||||
} else if (op == REDIS_OP_UNION) {
|
||||
dict *accumulator = dictCreate(&setDictType,NULL);
|
||||
dictIterator *di;
|
||||
dictEntry *de;
|
||||
double score;
|
||||
|
||||
if (setnum) {
|
||||
/* Our union is at least as large as the largest set.
|
||||
* Resize the dictionary ASAP to avoid useless rehashing. */
|
||||
dictExpand(accumulator,zuiLength(&src[setnum-1]));
|
||||
}
|
||||
|
||||
/* Step 1: Create a dictionary of elements -> aggregated-scores
|
||||
* by iterating one sorted set after the other. */
|
||||
for (i = 0; i < setnum; i++) {
|
||||
if (zuiLength(&src[i]) == 0)
|
||||
continue;
|
||||
if (zuiLength(&src[i]) == 0) continue;
|
||||
|
||||
zuiInitIterator(&src[i]);
|
||||
while (zuiNext(&src[i],&zval)) {
|
||||
double score, value;
|
||||
|
||||
/* Skip an element that when already processed */
|
||||
if (dictFind(dstzset->dict,zuiObjectFromValue(&zval)) != NULL)
|
||||
continue;
|
||||
|
||||
/* Initialize score */
|
||||
/* Initialize value */
|
||||
score = src[i].weight * zval.score;
|
||||
if (isnan(score)) score = 0;
|
||||
|
||||
/* We need to check only next sets to see if this element
|
||||
* exists, since we process every element just one time so
|
||||
* it can't exist in a previous set (otherwise it would be
|
||||
* already processed). */
|
||||
for (j = (i+1); j < setnum; j++) {
|
||||
/* It is not safe to access the zset we are
|
||||
* iterating, so explicitly check for equal object. */
|
||||
if(src[j].subject == src[i].subject) {
|
||||
value = zval.score*src[j].weight;
|
||||
zunionInterAggregate(&score,value,aggregate);
|
||||
} else if (zuiFind(&src[j],&zval,&value)) {
|
||||
value *= src[j].weight;
|
||||
zunionInterAggregate(&score,value,aggregate);
|
||||
/* Search for this element in the accumulating dictionary. */
|
||||
de = dictFind(accumulator,zuiObjectFromValue(&zval));
|
||||
/* If we don't have it, we need to create a new entry. */
|
||||
if (de == NULL) {
|
||||
tmp = zuiObjectFromValue(&zval);
|
||||
/* Remember the longest single element encountered,
|
||||
* to understand if it's possible to convert to ziplist
|
||||
* at the end. */
|
||||
if (tmp->encoding == REDIS_ENCODING_RAW) {
|
||||
if (sdslen(tmp->ptr) > maxelelen)
|
||||
maxelelen = sdslen(tmp->ptr);
|
||||
}
|
||||
/* Add the element with its initial score. */
|
||||
de = dictAddRaw(accumulator,tmp);
|
||||
incrRefCount(tmp);
|
||||
dictSetDoubleVal(de,score);
|
||||
} else {
|
||||
/* Update the score with the score of the new instance
|
||||
* of the element found in the current sorted set.
|
||||
*
|
||||
* Here we access directly the dictEntry double
|
||||
* value inside the union as it is a big speedup
|
||||
* compared to using the getDouble/setDouble API. */
|
||||
zunionInterAggregate(&de->v.d,score,aggregate);
|
||||
}
|
||||
|
||||
tmp = zuiObjectFromValue(&zval);
|
||||
znode = zslInsert(dstzset->zsl,score,tmp);
|
||||
incrRefCount(zval.ele); /* added to skiplist */
|
||||
dictAdd(dstzset->dict,tmp,&znode->score);
|
||||
incrRefCount(zval.ele); /* added to dictionary */
|
||||
|
||||
if (tmp->encoding == REDIS_ENCODING_RAW)
|
||||
if (sdslen(tmp->ptr) > maxelelen)
|
||||
maxelelen = sdslen(tmp->ptr);
|
||||
}
|
||||
zuiClearIterator(&src[i]);
|
||||
}
|
||||
|
||||
/* Step 2: convert the dictionary into the final sorted set. */
|
||||
di = dictGetIterator(accumulator);
|
||||
|
||||
/* We now are aware of the final size of the resulting sorted set,
|
||||
* let's resize the dictionary embedded inside the sorted set to the
|
||||
* right size, in order to save rehashing time. */
|
||||
dictExpand(dstzset->dict,dictSize(accumulator));
|
||||
|
||||
while((de = dictNext(di)) != NULL) {
|
||||
robj *ele = dictGetKey(de);
|
||||
score = dictGetDoubleVal(de);
|
||||
znode = zslInsert(dstzset->zsl,score,ele);
|
||||
incrRefCount(ele); /* added to skiplist */
|
||||
dictAdd(dstzset->dict,ele,&znode->score);
|
||||
incrRefCount(ele); /* added to dictionary */
|
||||
}
|
||||
dictReleaseIterator(di);
|
||||
|
||||
/* We can free the accumulator dictionary now. */
|
||||
dictRelease(accumulator);
|
||||
} else {
|
||||
redisPanic("Unknown operator");
|
||||
}
|
||||
|
||||
+85
-21
@@ -37,6 +37,7 @@
|
||||
#include <unistd.h>
|
||||
#include <sys/time.h>
|
||||
#include <float.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "util.h"
|
||||
|
||||
@@ -214,28 +215,91 @@ long long memtoll(const char *p, int *err) {
|
||||
return val*mul;
|
||||
}
|
||||
|
||||
/* Convert a long long into a string. Returns the number of
|
||||
* characters needed to represent the number, that can be shorter if passed
|
||||
* buffer length is not enough to store the whole number. */
|
||||
int ll2string(char *s, size_t len, long long value) {
|
||||
char buf[32], *p;
|
||||
unsigned long long v;
|
||||
size_t l;
|
||||
/* Return the number of digits of 'v' when converted to string in radix 10.
|
||||
* See ll2string() for more information. */
|
||||
uint32_t digits10(uint64_t v) {
|
||||
if (v < 10) return 1;
|
||||
if (v < 100) return 2;
|
||||
if (v < 1000) return 3;
|
||||
if (v < 1000000000000UL) {
|
||||
if (v < 100000000UL) {
|
||||
if (v < 1000000) {
|
||||
if (v < 10000) return 4;
|
||||
return 5 + (v >= 100000);
|
||||
}
|
||||
return 7 + (v >= 10000000UL);
|
||||
}
|
||||
if (v < 10000000000UL) {
|
||||
return 9 + (v >= 1000000000UL);
|
||||
}
|
||||
return 11 + (v >= 100000000000UL);
|
||||
}
|
||||
return 12 + digits10(v / 1000000000000UL);
|
||||
}
|
||||
|
||||
if (len == 0) return 0;
|
||||
v = (value < 0) ? -value : value;
|
||||
p = buf+31; /* point to the last character */
|
||||
do {
|
||||
*p-- = '0'+(v%10);
|
||||
v /= 10;
|
||||
} while(v);
|
||||
if (value < 0) *p-- = '-';
|
||||
p++;
|
||||
l = 32-(p-buf);
|
||||
if (l+1 > len) l = len-1; /* Make sure it fits, including the nul term */
|
||||
memcpy(s,p,l);
|
||||
s[l] = '\0';
|
||||
return l;
|
||||
/* Convert a long long into a string. Returns the number of
|
||||
* characters needed to represent the number.
|
||||
* If the buffer is not big enough to store the string, 0 is returned.
|
||||
*
|
||||
* Based on the following article (that apparently does not provide a
|
||||
* novel approach but only publicizes an already used technique):
|
||||
*
|
||||
* https://www.facebook.com/notes/facebook-engineering/three-optimization-tips-for-c/10151361643253920
|
||||
*
|
||||
* Modified in order to handle signed integers since the original code was
|
||||
* designed for unsigned integers. */
|
||||
int ll2string(char* dst, size_t dstlen, long long svalue) {
|
||||
static const char digits[201] =
|
||||
"0001020304050607080910111213141516171819"
|
||||
"2021222324252627282930313233343536373839"
|
||||
"4041424344454647484950515253545556575859"
|
||||
"6061626364656667686970717273747576777879"
|
||||
"8081828384858687888990919293949596979899";
|
||||
int negative;
|
||||
unsigned long long value;
|
||||
|
||||
/* The main loop works with 64bit unsigned integers for simplicity, so
|
||||
* we convert the number here and remember if it is negative. */
|
||||
if (svalue < 0) {
|
||||
if (svalue != LLONG_MIN) {
|
||||
value = -svalue;
|
||||
} else {
|
||||
value = ((unsigned long long) LLONG_MAX)+1;
|
||||
}
|
||||
negative = 1;
|
||||
} else {
|
||||
value = svalue;
|
||||
negative = 0;
|
||||
}
|
||||
|
||||
/* Check length. */
|
||||
uint32_t const length = digits10(value)+negative;
|
||||
if (length >= dstlen) return 0;
|
||||
|
||||
/* Null term. */
|
||||
uint32_t next = length;
|
||||
dst[next] = '\0';
|
||||
next--;
|
||||
while (value >= 100) {
|
||||
int const i = (value % 100) * 2;
|
||||
value /= 100;
|
||||
dst[next] = digits[i + 1];
|
||||
dst[next - 1] = digits[i];
|
||||
next -= 2;
|
||||
}
|
||||
|
||||
/* Handle last 1-2 digits. */
|
||||
if (value < 10) {
|
||||
dst[next] = '0' + (uint32_t) value;
|
||||
} else {
|
||||
int i = (uint32_t) value * 2;
|
||||
dst[next] = digits[i + 1];
|
||||
dst[next - 1] = digits[i];
|
||||
}
|
||||
|
||||
/* Add sign. */
|
||||
if (negative) dst[0] = '-';
|
||||
return length;
|
||||
}
|
||||
|
||||
/* Convert a string into a long long. Returns 1 if the string could be parsed
|
||||
|
||||
+1
-1
@@ -1 +1 @@
|
||||
#define REDIS_VERSION "2.8.12"
|
||||
#define REDIS_VERSION "2.8.16"
|
||||
|
||||
+13
-13
@@ -576,19 +576,19 @@ static unsigned char *__ziplistDelete(unsigned char *zl, unsigned char *p, unsig
|
||||
|
||||
/* Insert item at "p". */
|
||||
static unsigned char *__ziplistInsert(unsigned char *zl, unsigned char *p, unsigned char *s, unsigned int slen) {
|
||||
size_t curlen = intrev32ifbe(ZIPLIST_BYTES(zl)), reqlen, prevlen = 0;
|
||||
size_t curlen = intrev32ifbe(ZIPLIST_BYTES(zl)), reqlen;
|
||||
unsigned int prevlensize, prevlen = 0;
|
||||
size_t offset;
|
||||
int nextdiff = 0;
|
||||
unsigned char encoding = 0;
|
||||
long long value = 123456789; /* initialized to avoid warning. Using a value
|
||||
that is easy to see if for some reason
|
||||
we use it uninitialized. */
|
||||
zlentry entry, tail;
|
||||
zlentry tail;
|
||||
|
||||
/* Find out prevlen for the entry that is inserted. */
|
||||
if (p[0] != ZIP_END) {
|
||||
entry = zipEntry(p);
|
||||
prevlen = entry.prevrawlen;
|
||||
ZIP_DECODE_PREVLEN(p, prevlensize, prevlen);
|
||||
} else {
|
||||
unsigned char *ptail = ZIPLIST_ENTRY_TAIL(zl);
|
||||
if (ptail[0] != ZIP_END) {
|
||||
@@ -676,15 +676,15 @@ unsigned char *ziplistPush(unsigned char *zl, unsigned char *s, unsigned int sle
|
||||
* doesn't contain an element at the provided index, NULL is returned. */
|
||||
unsigned char *ziplistIndex(unsigned char *zl, int index) {
|
||||
unsigned char *p;
|
||||
zlentry entry;
|
||||
unsigned int prevlensize, prevlen = 0;
|
||||
if (index < 0) {
|
||||
index = (-index)-1;
|
||||
p = ZIPLIST_ENTRY_TAIL(zl);
|
||||
if (p[0] != ZIP_END) {
|
||||
entry = zipEntry(p);
|
||||
while (entry.prevrawlen > 0 && index--) {
|
||||
p -= entry.prevrawlen;
|
||||
entry = zipEntry(p);
|
||||
ZIP_DECODE_PREVLEN(p, prevlensize, prevlen);
|
||||
while (prevlen > 0 && index--) {
|
||||
p -= prevlen;
|
||||
ZIP_DECODE_PREVLEN(p, prevlensize, prevlen);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -722,7 +722,7 @@ unsigned char *ziplistNext(unsigned char *zl, unsigned char *p) {
|
||||
|
||||
/* Return pointer to previous entry in ziplist. */
|
||||
unsigned char *ziplistPrev(unsigned char *zl, unsigned char *p) {
|
||||
zlentry entry;
|
||||
unsigned int prevlensize, prevlen = 0;
|
||||
|
||||
/* Iterating backwards from ZIP_END should return the tail. When "p" is
|
||||
* equal to the first element of the list, we're already at the head,
|
||||
@@ -733,9 +733,9 @@ unsigned char *ziplistPrev(unsigned char *zl, unsigned char *p) {
|
||||
} else if (p == ZIPLIST_ENTRY_HEAD(zl)) {
|
||||
return NULL;
|
||||
} else {
|
||||
entry = zipEntry(p);
|
||||
assert(entry.prevrawlen > 0);
|
||||
return p-entry.prevrawlen;
|
||||
ZIP_DECODE_PREVLEN(p, prevlensize, prevlen);
|
||||
assert(prevlen > 0);
|
||||
return p-prevlen;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -56,7 +56,7 @@
|
||||
* signal the end of the hash. The special value 254 is used to mark
|
||||
* empty space that can be used to add new key/value pairs.
|
||||
*
|
||||
* <free> is the number of free unused bytes after the string, resulting
|
||||
* <free> is the number of free unused bytes after the string, resulting
|
||||
* from modification of values associated to a key. For instance if "foo"
|
||||
* is set to "bar", and later "foo" will be set to "hi", it will have a
|
||||
* free byte to use if the value will enlarge again later, or even in
|
||||
@@ -186,7 +186,7 @@ static unsigned int zipmapRawKeyLength(unsigned char *p) {
|
||||
static unsigned int zipmapRawValueLength(unsigned char *p) {
|
||||
unsigned int l = zipmapDecodeLength(p);
|
||||
unsigned int used;
|
||||
|
||||
|
||||
used = zipmapEncodeLength(NULL,l);
|
||||
used += p[used] + 1 + l;
|
||||
return used;
|
||||
@@ -214,7 +214,7 @@ unsigned char *zipmapSet(unsigned char *zm, unsigned char *key, unsigned int kle
|
||||
unsigned int freelen, reqlen = zipmapRequiredLength(klen,vlen);
|
||||
unsigned int empty, vempty;
|
||||
unsigned char *p;
|
||||
|
||||
|
||||
freelen = reqlen;
|
||||
if (update) *update = 0;
|
||||
p = zipmapLookupRaw(zm,key,klen,&zmlen);
|
||||
|
||||
+6
-3
@@ -67,7 +67,10 @@ void zlibc_free(void *ptr) {
|
||||
#define free(ptr) je_free(ptr)
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_ATOMIC
|
||||
#if defined(__ATOMIC_RELAXED)
|
||||
#define update_zmalloc_stat_add(__n) __atomic_add_fetch(&used_memory, (__n), __ATOMIC_RELAXED)
|
||||
#define update_zmalloc_stat_sub(__n) __atomic_sub_fetch(&used_memory, (__n), __ATOMIC_RELAXED)
|
||||
#elif defined(HAVE_ATOMIC)
|
||||
#define update_zmalloc_stat_add(__n) __sync_add_and_fetch(&used_memory, (__n))
|
||||
#define update_zmalloc_stat_sub(__n) __sync_sub_and_fetch(&used_memory, (__n))
|
||||
#else
|
||||
@@ -219,8 +222,8 @@ size_t zmalloc_used_memory(void) {
|
||||
size_t um;
|
||||
|
||||
if (zmalloc_thread_safe) {
|
||||
#ifdef HAVE_ATOMIC
|
||||
um = __sync_add_and_fetch(&used_memory, 0);
|
||||
#if defined(__ATOMIC_RELAXED) || defined(HAVE_ATOMIC)
|
||||
um = update_zmalloc_stat_add(0);
|
||||
#else
|
||||
pthread_mutex_lock(&used_memory_mutex);
|
||||
um = used_memory;
|
||||
|
||||
@@ -9,6 +9,7 @@ bind 127.0.0.1
|
||||
loglevel verbose
|
||||
logfile ''
|
||||
databases 16
|
||||
latency-monitor-threshold 1
|
||||
|
||||
save 900 1
|
||||
save 300 10
|
||||
|
||||
+1
-1
@@ -396,7 +396,7 @@ proc restart_instance {type id} {
|
||||
|
||||
# Check that the instance is running
|
||||
if {[server_is_up 127.0.0.1 $port 100] == 0} {
|
||||
abort_sentinel_test "Problems starting $type #$j: ping timeout"
|
||||
abort_sentinel_test "Problems starting $type #$id: ping timeout"
|
||||
}
|
||||
|
||||
# Connect with it with a fresh link
|
||||
|
||||
@@ -23,6 +23,84 @@ proc start_server_aof {overrides code} {
|
||||
}
|
||||
|
||||
tags {"aof"} {
|
||||
## Server can start when aof-load-truncated is set to yes and AOF
|
||||
## is truncated, with an incomplete MULTI block.
|
||||
create_aof {
|
||||
append_to_aof [formatCommand set foo hello]
|
||||
append_to_aof [formatCommand multi]
|
||||
append_to_aof [formatCommand set bar world]
|
||||
}
|
||||
|
||||
start_server_aof [list dir $server_path aof-load-truncated yes] {
|
||||
test "Unfinished MULTI: Server should start if load-truncated is yes" {
|
||||
assert_equal 1 [is_alive $srv]
|
||||
}
|
||||
}
|
||||
|
||||
## Should also start with truncated AOF without incomplete MULTI block.
|
||||
create_aof {
|
||||
append_to_aof [formatCommand incr foo]
|
||||
append_to_aof [formatCommand incr foo]
|
||||
append_to_aof [formatCommand incr foo]
|
||||
append_to_aof [formatCommand incr foo]
|
||||
append_to_aof [formatCommand incr foo]
|
||||
append_to_aof [string range [formatCommand incr foo] 0 end-1]
|
||||
}
|
||||
|
||||
start_server_aof [list dir $server_path aof-load-truncated yes] {
|
||||
test "Short read: Server should start if load-truncated is yes" {
|
||||
assert_equal 1 [is_alive $srv]
|
||||
}
|
||||
|
||||
set client [redis [dict get $srv host] [dict get $srv port]]
|
||||
|
||||
test "Truncated AOF loaded: we expect foo to be equal to 5" {
|
||||
assert {[$client get foo] eq "5"}
|
||||
}
|
||||
|
||||
test "Append a new command after loading an incomplete AOF" {
|
||||
$client incr foo
|
||||
}
|
||||
}
|
||||
|
||||
# Now the AOF file is expected to be correct
|
||||
start_server_aof [list dir $server_path aof-load-truncated yes] {
|
||||
test "Short read + command: Server should start" {
|
||||
assert_equal 1 [is_alive $srv]
|
||||
}
|
||||
|
||||
set client [redis [dict get $srv host] [dict get $srv port]]
|
||||
|
||||
test "Truncated AOF loaded: we expect foo to be equal to 6 now" {
|
||||
assert {[$client get foo] eq "6"}
|
||||
}
|
||||
}
|
||||
|
||||
## Test that the server exits when the AOF contains a format error
|
||||
create_aof {
|
||||
append_to_aof [formatCommand set foo hello]
|
||||
append_to_aof "!!!"
|
||||
append_to_aof [formatCommand set foo hello]
|
||||
}
|
||||
|
||||
start_server_aof [list dir $server_path aof-load-truncated yes] {
|
||||
test "Bad format: Server should have logged an error" {
|
||||
set pattern "*Bad file format reading the append only file*"
|
||||
set retry 10
|
||||
while {$retry} {
|
||||
set result [exec tail -n1 < [dict get $srv stdout]]
|
||||
if {[string match $pattern $result]} {
|
||||
break
|
||||
}
|
||||
incr retry -1
|
||||
after 1000
|
||||
}
|
||||
if {$retry == 0} {
|
||||
error "assertion:expected error not found on config file"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
## Test the server doesn't start when the AOF contains an unfinished MULTI
|
||||
create_aof {
|
||||
append_to_aof [formatCommand set foo hello]
|
||||
@@ -30,7 +108,7 @@ tags {"aof"} {
|
||||
append_to_aof [formatCommand set bar world]
|
||||
}
|
||||
|
||||
start_server_aof [list dir $server_path] {
|
||||
start_server_aof [list dir $server_path aof-load-truncated no] {
|
||||
test "Unfinished MULTI: Server should have logged an error" {
|
||||
set pattern "*Unexpected end of file reading the append only file*"
|
||||
set retry 10
|
||||
@@ -54,9 +132,9 @@ tags {"aof"} {
|
||||
append_to_aof [string range [formatCommand set bar world] 0 end-1]
|
||||
}
|
||||
|
||||
start_server_aof [list dir $server_path] {
|
||||
start_server_aof [list dir $server_path aof-load-truncated no] {
|
||||
test "Short read: Server should have logged an error" {
|
||||
set pattern "*Bad file format reading the append only file*"
|
||||
set pattern "*Unexpected end of file reading the append only file*"
|
||||
set retry 10
|
||||
while {$retry} {
|
||||
set result [exec tail -n1 < [dict get $srv stdout]]
|
||||
@@ -86,7 +164,7 @@ tags {"aof"} {
|
||||
}
|
||||
|
||||
## Test that the server can be started using the truncated AOF
|
||||
start_server_aof [list dir $server_path] {
|
||||
start_server_aof [list dir $server_path aof-load-truncated no] {
|
||||
test "Fixed AOF: Server should have been started" {
|
||||
assert_equal 1 [is_alive $srv]
|
||||
}
|
||||
@@ -110,7 +188,7 @@ tags {"aof"} {
|
||||
append_to_aof [formatCommand spop set]
|
||||
}
|
||||
|
||||
start_server_aof [list dir $server_path] {
|
||||
start_server_aof [list dir $server_path aof-load-truncated no] {
|
||||
test "AOF+SPOP: Server should have been started" {
|
||||
assert_equal 1 [is_alive $srv]
|
||||
}
|
||||
@@ -133,7 +211,7 @@ tags {"aof"} {
|
||||
append_to_aof [formatCommand rpush list bar]
|
||||
}
|
||||
|
||||
start_server_aof [list dir $server_path] {
|
||||
start_server_aof [list dir $server_path aof-load-truncated no] {
|
||||
test "AOF+EXPIRE: Server should have been started" {
|
||||
assert_equal 1 [is_alive $srv]
|
||||
}
|
||||
|
||||
@@ -29,15 +29,25 @@ proc test_psync {descr duration backlog_size backlog_ttl delay cond} {
|
||||
set load_handle1 [start_bg_complex_data $master_host $master_port 11 100000]
|
||||
set load_handle2 [start_bg_complex_data $master_host $master_port 12 100000]
|
||||
|
||||
test {First server should have role slave after SLAVEOF} {
|
||||
test {Slave should be able to synchronize with the master} {
|
||||
$slave slaveof $master_host $master_port
|
||||
wait_for_condition 50 100 {
|
||||
[s 0 role] eq {slave}
|
||||
[lindex [r role] 0] eq {slave} &&
|
||||
[lindex [r role] 3] eq {connected}
|
||||
} else {
|
||||
fail "Replication not started."
|
||||
}
|
||||
}
|
||||
|
||||
# Check that the background clients are actually writing.
|
||||
test {Detect write load to master} {
|
||||
wait_for_condition 50 100 {
|
||||
[$master dbsize] > 100
|
||||
} else {
|
||||
fail "Can't detect write load from background clients."
|
||||
}
|
||||
}
|
||||
|
||||
test "Test replication partial resync: $descr" {
|
||||
# Now while the clients are writing data, break the maste-slave
|
||||
# link multiple times.
|
||||
@@ -92,7 +102,7 @@ test_psync {ok psync} 6 1000000 3600 0 {
|
||||
assert {[s -1 sync_partial_ok] > 0}
|
||||
}
|
||||
|
||||
test_psync {no backlog} 6 100 3600 0 {
|
||||
test_psync {no backlog} 6 100 3600 0.5 {
|
||||
assert {[s -1 sync_partial_err] > 0}
|
||||
}
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ start_server {tags {"repl"}} {
|
||||
assert {$offset > 0}
|
||||
assert {[llength $slaves] == 1}
|
||||
lassign [lindex $slaves 0] master_host master_port slave_offset
|
||||
assert {$slave_offset < $offset}
|
||||
assert {$slave_offset <= $offset}
|
||||
}
|
||||
|
||||
test {ROLE in slave reports slave in connected state} {
|
||||
@@ -94,15 +94,6 @@ start_server {tags {"repl"}} {
|
||||
}
|
||||
}
|
||||
|
||||
proc start_write_load {host port seconds} {
|
||||
set tclsh [info nameofexecutable]
|
||||
exec $tclsh tests/helpers/gen_write_load.tcl $host $port $seconds &
|
||||
}
|
||||
|
||||
proc stop_write_load {handle} {
|
||||
catch {exec /bin/kill -9 $handle}
|
||||
}
|
||||
|
||||
start_server {tags {"repl"}} {
|
||||
set master [srv 0 client]
|
||||
set master_host [srv 0 host]
|
||||
|
||||
+13
-1
@@ -351,9 +351,21 @@ proc colorstr {color str} {
|
||||
default {set colorcode {37}}
|
||||
}
|
||||
if {$colorcode ne {}} {
|
||||
return "\033\[$b;${colorcode};40m$str\033\[0m"
|
||||
return "\033\[$b;${colorcode};49m$str\033\[0m"
|
||||
}
|
||||
} else {
|
||||
return $str
|
||||
}
|
||||
}
|
||||
|
||||
# Execute a background process writing random data for the specified number
|
||||
# of seconds to the specified Redis instance.
|
||||
proc start_write_load {host port seconds} {
|
||||
set tclsh [info nameofexecutable]
|
||||
exec $tclsh tests/helpers/gen_write_load.tcl $host $port $seconds &
|
||||
}
|
||||
|
||||
# Stop a process generating write load executed with start_write_load.
|
||||
proc stop_write_load {handle} {
|
||||
catch {exec /bin/kill -9 $handle}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,67 @@
|
||||
start_server {tags {"aofrw"}} {
|
||||
# Enable the AOF
|
||||
r config set appendonly yes
|
||||
r config set auto-aof-rewrite-percentage 0 ; # Disable auto-rewrite.
|
||||
waitForBgrewriteaof r
|
||||
|
||||
test {AOF rewrite during write load} {
|
||||
# Start a write load for 10 seconds
|
||||
set master [srv 0 client]
|
||||
set master_host [srv 0 host]
|
||||
set master_port [srv 0 port]
|
||||
set load_handle0 [start_write_load $master_host $master_port 10]
|
||||
set load_handle1 [start_write_load $master_host $master_port 10]
|
||||
set load_handle2 [start_write_load $master_host $master_port 10]
|
||||
set load_handle3 [start_write_load $master_host $master_port 10]
|
||||
set load_handle4 [start_write_load $master_host $master_port 10]
|
||||
|
||||
# Make sure the instance is really receiving data
|
||||
wait_for_condition 50 100 {
|
||||
[r dbsize] > 0
|
||||
} else {
|
||||
fail "No write load detected."
|
||||
}
|
||||
|
||||
# After 3 seconds, start a rewrite, while the write load is still
|
||||
# active.
|
||||
after 3000
|
||||
r bgrewriteaof
|
||||
waitForBgrewriteaof r
|
||||
|
||||
# Let it run a bit more so that we'll append some data to the new
|
||||
# AOF.
|
||||
after 1000
|
||||
|
||||
# Stop the processes generating the load if they are still active
|
||||
stop_write_load $load_handle0
|
||||
stop_write_load $load_handle1
|
||||
stop_write_load $load_handle2
|
||||
stop_write_load $load_handle3
|
||||
stop_write_load $load_handle4
|
||||
|
||||
# Make sure that we remain the only connected client.
|
||||
# This step is needed to make sure there are no pending writes
|
||||
# that will be processed between the two "debug digest" calls.
|
||||
wait_for_condition 50 100 {
|
||||
[llength [split [string trim [r client list]] "\n"]] == 1
|
||||
} else {
|
||||
puts [r client list]
|
||||
fail "Clients generating loads are not disconnecting"
|
||||
}
|
||||
|
||||
# Get the data set digest
|
||||
set d1 [r debug digest]
|
||||
|
||||
# Load the AOF
|
||||
r debug loadaof
|
||||
set d2 [r debug digest]
|
||||
|
||||
# Make sure they are the same
|
||||
assert {$d1 eq $d2}
|
||||
}
|
||||
}
|
||||
|
||||
start_server {tags {"aofrw"}} {
|
||||
test {Turning off AOF kills the background writing child if any} {
|
||||
r config set appendonly yes
|
||||
waitForBgrewriteaof r
|
||||
|
||||
@@ -404,6 +404,12 @@ start_server {tags {"basic"}} {
|
||||
r move mykey 10
|
||||
} {0}
|
||||
|
||||
test {MOVE against non-integer DB (#1428)} {
|
||||
r set mykey hello
|
||||
catch {r move mykey notanumber} e
|
||||
set e
|
||||
} {*ERR*index out of range}
|
||||
|
||||
test {SET/GET keys in different DBs} {
|
||||
r set a hello
|
||||
r set b world
|
||||
@@ -769,4 +775,9 @@ start_server {tags {"basic"}} {
|
||||
r keys *
|
||||
r keys *
|
||||
} {dlskeriewrioeuwqoirueioqwrueoqwrueqw}
|
||||
|
||||
test {GETRANGE with huge ranges, Github issue #1844} {
|
||||
r set foo bar
|
||||
r getrange foo 0 4294967297
|
||||
} {bar}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
start_server {tags {"latency-monitor"}} {
|
||||
# Set a threshold high enough to avoid spurious latency events.
|
||||
r config set latency-monitor-threshold 200
|
||||
r latency reset
|
||||
|
||||
test {Test latency events logging} {
|
||||
r debug sleep 0.3
|
||||
after 1100
|
||||
r debug sleep 0.4
|
||||
after 1100
|
||||
r debug sleep 0.5
|
||||
assert {[r latency history command] >= 3}
|
||||
}
|
||||
|
||||
test {LATENCY HISTORY output is ok} {
|
||||
set min 250
|
||||
set max 450
|
||||
foreach event [r latency history command] {
|
||||
lassign $event time latency
|
||||
assert {$latency >= $min && $latency <= $max}
|
||||
incr min 100
|
||||
incr max 100
|
||||
set last_time $time ; # Used in the next test
|
||||
}
|
||||
}
|
||||
|
||||
test {LATENCY LATEST output is ok} {
|
||||
foreach event [r latency latest] {
|
||||
lassign $event eventname time latency max
|
||||
assert {$eventname eq "command"}
|
||||
assert {$max >= 450 & $max <= 650}
|
||||
assert {$time == $last_time}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
test {LATENCY HISTORY / RESET with wrong event name is fine} {
|
||||
assert {[llength [r latency history blabla]] == 0}
|
||||
assert {[r latency reset blabla] == 0}
|
||||
}
|
||||
|
||||
test {LATENCY DOCTOR produces some output} {
|
||||
assert {[string length [r latency doctor]] > 0}
|
||||
}
|
||||
|
||||
test {LATENCY RESET is able to reset events} {
|
||||
assert {[r latency reset] > 0}
|
||||
assert {[r latency latest] eq {}}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,28 @@
|
||||
start_server {tags {"maxmemory"}} {
|
||||
test "Without maxmemory small integers are shared" {
|
||||
r config set maxmemory 0
|
||||
r set a 1
|
||||
assert {[r object refcount a] > 1}
|
||||
}
|
||||
|
||||
test "With maxmemory and non-LRU policy integers are still shared" {
|
||||
r config set maxmemory 1073741824
|
||||
r config set maxmemory-policy allkeys-random
|
||||
r set a 1
|
||||
assert {[r object refcount a] > 1}
|
||||
}
|
||||
|
||||
test "With maxmemory and LRU policy integers are not shared" {
|
||||
r config set maxmemory 1073741824
|
||||
r config set maxmemory-policy allkeys-lru
|
||||
r set a 1
|
||||
r config set maxmemory-policy volatile-lru
|
||||
r set b 1
|
||||
assert {[r object refcount a] == 1}
|
||||
assert {[r object refcount b] == 1}
|
||||
r config set maxmemory 0
|
||||
}
|
||||
|
||||
foreach policy {
|
||||
allkeys-random allkeys-lru volatile-lru volatile-random volatile-ttl
|
||||
} {
|
||||
|
||||
@@ -47,6 +47,22 @@ start_server {tags {"pubsub"}} {
|
||||
__consume_subscribe_messages $client punsubscribe $channels
|
||||
}
|
||||
|
||||
test "Pub/Sub PING" {
|
||||
set rd1 [redis_deferring_client]
|
||||
subscribe $rd1 somechannel
|
||||
# While subscribed to non-zero channels PING works in Pub/Sub mode.
|
||||
$rd1 ping
|
||||
$rd1 ping "foo"
|
||||
set reply1 [$rd1 read]
|
||||
set reply2 [$rd1 read]
|
||||
unsubscribe $rd1 somechannel
|
||||
# Now we are unsubscribed, PING should just return PONG.
|
||||
$rd1 ping
|
||||
set reply3 [$rd1 read]
|
||||
$rd1 close
|
||||
list $reply1 $reply2 $reply3
|
||||
} {{pong {}} {pong foo} PONG}
|
||||
|
||||
test "PUBLISH/SUBSCRIBE basics" {
|
||||
set rd1 [redis_deferring_client]
|
||||
|
||||
@@ -180,6 +196,10 @@ start_server {tags {"pubsub"}} {
|
||||
$rd1 close
|
||||
}
|
||||
|
||||
test "NUMSUB returns numbers, not strings (#1561)" {
|
||||
r pubsub numsub abc def
|
||||
} {abc 0 def 0}
|
||||
|
||||
test "Mix SUBSCRIBE and PSUBSCRIBE" {
|
||||
set rd1 [redis_deferring_client]
|
||||
assert_equal {1} [subscribe $rd1 {foo.bar}]
|
||||
|
||||
@@ -358,6 +358,23 @@ start_server {tags {"scripting"}} {
|
||||
return redis.call("get", "key")
|
||||
} 0
|
||||
} {12039611435714932082}
|
||||
|
||||
test {Verify negative arg count is error instead of crash (issue #1842)} {
|
||||
catch { r eval { return "hello" } -12 } e
|
||||
set e
|
||||
} {ERR Number of keys can't be negative}
|
||||
|
||||
test {Correct handling of reused argv (issue #1939)} {
|
||||
r eval {
|
||||
for i = 0, 10 do
|
||||
redis.call('SET', 'a', '1')
|
||||
redis.call('MGET', 'a', 'b', 'c')
|
||||
redis.call('EXPIRE', 'a', 0)
|
||||
redis.call('GET', 'a')
|
||||
redis.call('MGET', 'a', 'b', 'c')
|
||||
end
|
||||
} 0
|
||||
}
|
||||
}
|
||||
|
||||
# Start a new server since the last test in this stanza will kill the
|
||||
|
||||
@@ -588,6 +588,28 @@ start_server {tags {"zset"}} {
|
||||
r zrange to_here 0 -1
|
||||
} {100}
|
||||
|
||||
test {ZUNIONSTORE result is sorted} {
|
||||
# Create two sets with common and not common elements, perform
|
||||
# the UNION, check that elements are still sorted.
|
||||
r del one two dest
|
||||
set cmd1 [list r zadd one]
|
||||
set cmd2 [list r zadd two]
|
||||
for {set j 0} {$j < 1000} {incr j} {
|
||||
lappend cmd1 [expr rand()] [randomInt 1000]
|
||||
lappend cmd2 [expr rand()] [randomInt 1000]
|
||||
}
|
||||
{*}$cmd1
|
||||
{*}$cmd2
|
||||
assert {[r zcard one] > 100}
|
||||
assert {[r zcard two] > 100}
|
||||
r zunionstore dest 2 one two
|
||||
set oldscore 0
|
||||
foreach {ele score} [r zrange dest 0 -1 withscores] {
|
||||
assert {$score >= $oldscore}
|
||||
set oldscore $score
|
||||
}
|
||||
}
|
||||
|
||||
proc stressers {encoding} {
|
||||
if {$encoding == "ziplist"} {
|
||||
# Little extra to allow proper fuzzing in the sorting stresser
|
||||
|
||||
@@ -50,7 +50,7 @@ def commands
|
||||
require "json"
|
||||
require "uri"
|
||||
|
||||
url = URI.parse "https://raw.github.com/antirez/redis-doc/master/commands.json"
|
||||
url = URI.parse "https://raw.githubusercontent.com/antirez/redis-doc/master/commands.json"
|
||||
client = Net::HTTP.new url.host, url.port
|
||||
client.use_ssl = true
|
||||
response = client.get url.path
|
||||
|
||||
@@ -152,7 +152,7 @@ rm -f $TMP_FILE
|
||||
#we hard code the configs here to avoid issues with templates containing env vars
|
||||
#kinda lame but works!
|
||||
REDIS_INIT_HEADER=\
|
||||
"#/bin/sh\n
|
||||
"#!/bin/sh\n
|
||||
#Configurations injected by install_server below....\n\n
|
||||
EXEC=$REDIS_EXECUTABLE\n
|
||||
CLIEXEC=$CLI_EXEC\n
|
||||
@@ -193,7 +193,7 @@ fi
|
||||
# warning if init info is not available.
|
||||
|
||||
cat > ${TMP_FILE} <<EOT
|
||||
#/bin/sh
|
||||
#!/bin/sh
|
||||
#Configurations injected by install_server below....
|
||||
|
||||
EXEC=$REDIS_EXECUTABLE
|
||||
|
||||
Reference in New Issue
Block a user