2011-06-30 07:27:32 -04:00
|
|
|
/* Slowlog implements a system that is able to remember the latest N
|
|
|
|
* queries that took more than M microseconds to execute.
|
|
|
|
*
|
|
|
|
* The execution time to reach to be logged in the slow log is set
|
|
|
|
* using the 'slowlog-log-slower-than' config directive, that is also
|
|
|
|
* readable and writable using the CONFIG SET/GET command.
|
|
|
|
*
|
|
|
|
* The slow queries log is actually not "logged" in the Redis log file
|
2012-11-08 12:25:23 -05:00
|
|
|
* but is accessible thanks to the SLOWLOG command.
|
|
|
|
*
|
|
|
|
* ----------------------------------------------------------------------------
|
|
|
|
*
|
|
|
|
* Copyright (c) 2009-2012, 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.
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
2015-07-26 09:14:57 -04:00
|
|
|
#include "server.h"
|
2012-11-08 12:25:23 -05:00
|
|
|
#include "slowlog.h"
|
2011-06-30 07:27:32 -04:00
|
|
|
|
|
|
|
/* Create a new slowlog entry.
|
|
|
|
* Incrementing the ref count of all the objects retained is up to
|
|
|
|
* this function. */
|
2017-06-15 06:57:54 -04:00
|
|
|
slowlogEntry *slowlogCreateEntry(client *c, robj **argv, int argc, long long duration) {
|
2011-06-30 07:27:32 -04:00
|
|
|
slowlogEntry *se = zmalloc(sizeof(*se));
|
2012-04-21 13:20:03 -04:00
|
|
|
int j, slargc = argc;
|
|
|
|
|
|
|
|
if (slargc > SLOWLOG_ENTRY_MAX_ARGC) slargc = SLOWLOG_ENTRY_MAX_ARGC;
|
|
|
|
se->argc = slargc;
|
|
|
|
se->argv = zmalloc(sizeof(robj*)*slargc);
|
|
|
|
for (j = 0; j < slargc; j++) {
|
|
|
|
/* Logging too many arguments is a useless memory waste, so we stop
|
|
|
|
* at SLOWLOG_ENTRY_MAX_ARGC, but use the last argument to specify
|
|
|
|
* how many remaining arguments there were in the original command. */
|
|
|
|
if (slargc != argc && j == slargc-1) {
|
2015-07-26 09:28:00 -04:00
|
|
|
se->argv[j] = createObject(OBJ_STRING,
|
2012-04-21 13:20:03 -04:00
|
|
|
sdscatprintf(sdsempty(),"... (%d more arguments)",
|
|
|
|
argc-slargc+1));
|
|
|
|
} else {
|
|
|
|
/* Trim too long strings as well... */
|
2015-07-26 09:28:00 -04:00
|
|
|
if (argv[j]->type == OBJ_STRING &&
|
2012-06-05 15:50:10 -04:00
|
|
|
sdsEncodedObject(argv[j]) &&
|
2012-04-21 13:20:03 -04:00
|
|
|
sdslen(argv[j]->ptr) > SLOWLOG_ENTRY_MAX_STRING)
|
|
|
|
{
|
|
|
|
sds s = sdsnewlen(argv[j]->ptr, SLOWLOG_ENTRY_MAX_STRING);
|
2011-06-30 07:27:32 -04:00
|
|
|
|
2012-04-21 13:20:03 -04:00
|
|
|
s = sdscatprintf(s,"... (%lu more bytes)",
|
|
|
|
(unsigned long)
|
|
|
|
sdslen(argv[j]->ptr) - SLOWLOG_ENTRY_MAX_STRING);
|
2015-07-26 09:28:00 -04:00
|
|
|
se->argv[j] = createObject(OBJ_STRING,s);
|
2017-09-21 02:19:21 -04:00
|
|
|
} else if (argv[j]->refcount == OBJ_SHARED_REFCOUNT) {
|
2012-04-21 13:20:03 -04:00
|
|
|
se->argv[j] = argv[j];
|
2017-09-21 02:19:21 -04:00
|
|
|
} else {
|
2017-09-21 06:35:04 -04:00
|
|
|
/* Here we need to dupliacate the string objects composing the
|
|
|
|
* argument vector of the command, because those may otherwise
|
|
|
|
* end shared with string objects stored into keys. Having
|
|
|
|
* shared objects between any part of Redis, and the data
|
|
|
|
* structure holding the data, is a problem: FLUSHALL ASYNC
|
|
|
|
* may release the shared string object and create a race. */
|
2017-09-21 02:19:21 -04:00
|
|
|
se->argv[j] = dupStringObject(argv[j]);
|
2012-04-21 13:20:03 -04:00
|
|
|
}
|
|
|
|
}
|
2011-06-30 07:27:32 -04:00
|
|
|
}
|
|
|
|
se->time = time(NULL);
|
|
|
|
se->duration = duration;
|
2011-06-30 11:36:15 -04:00
|
|
|
se->id = server.slowlog_entry_id++;
|
2017-06-15 06:57:54 -04:00
|
|
|
se->peerid = sdsnew(getClientPeerId(c));
|
|
|
|
se->cname = c->name ? sdsnew(c->name->ptr) : sdsempty();
|
2011-06-30 07:27:32 -04:00
|
|
|
return se;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Free a slow log entry. The argument is void so that the prototype of this
|
|
|
|
* function matches the one of the 'free' method of adlist.c.
|
|
|
|
*
|
|
|
|
* This function will take care to release all the retained object. */
|
|
|
|
void slowlogFreeEntry(void *septr) {
|
|
|
|
slowlogEntry *se = septr;
|
|
|
|
int j;
|
|
|
|
|
|
|
|
for (j = 0; j < se->argc; j++)
|
|
|
|
decrRefCount(se->argv[j]);
|
|
|
|
zfree(se->argv);
|
2017-06-15 06:57:54 -04:00
|
|
|
sdsfree(se->peerid);
|
|
|
|
sdsfree(se->cname);
|
2011-06-30 07:27:32 -04:00
|
|
|
zfree(se);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Initialize the slow log. This function should be called a single time
|
|
|
|
* at server startup. */
|
|
|
|
void slowlogInit(void) {
|
|
|
|
server.slowlog = listCreate();
|
2011-06-30 11:36:15 -04:00
|
|
|
server.slowlog_entry_id = 0;
|
2011-06-30 07:27:32 -04:00
|
|
|
listSetFreeMethod(server.slowlog,slowlogFreeEntry);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Push a new entry into the slow log.
|
|
|
|
* This function will make sure to trim the slow log accordingly to the
|
|
|
|
* configured max length. */
|
2017-06-15 06:57:54 -04:00
|
|
|
void slowlogPushEntryIfNeeded(client *c, robj **argv, int argc, long long duration) {
|
2011-06-30 09:47:15 -04:00
|
|
|
if (server.slowlog_log_slower_than < 0) return; /* Slowlog disabled */
|
2011-06-30 09:54:05 -04:00
|
|
|
if (duration >= server.slowlog_log_slower_than)
|
2017-06-15 06:57:54 -04:00
|
|
|
listAddNodeHead(server.slowlog,
|
|
|
|
slowlogCreateEntry(c,argv,argc,duration));
|
2011-06-30 07:27:32 -04:00
|
|
|
|
|
|
|
/* Remove old entries if needed. */
|
|
|
|
while (listLength(server.slowlog) > server.slowlog_max_len)
|
|
|
|
listDelNode(server.slowlog,listLast(server.slowlog));
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Remove all the entries from the current slow log. */
|
|
|
|
void slowlogReset(void) {
|
|
|
|
while (listLength(server.slowlog) > 0)
|
|
|
|
listDelNode(server.slowlog,listLast(server.slowlog));
|
|
|
|
}
|
|
|
|
|
|
|
|
/* The SLOWLOG command. Implements all the subcommands needed to handle the
|
|
|
|
* Redis slow log. */
|
2015-07-26 09:20:46 -04:00
|
|
|
void slowlogCommand(client *c) {
|
2017-11-29 17:30:30 -05:00
|
|
|
if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
|
2017-11-27 10:57:44 -05:00
|
|
|
const char *help[] = {
|
2018-06-09 14:03:52 -04:00
|
|
|
"GET [count] -- Return top entries from the slowlog (default: 10)."
|
2017-12-06 06:05:11 -05:00
|
|
|
" Entries are made of:",
|
|
|
|
" id, timestamp, time in microseconds, arguments array, client IP and port, client name",
|
2018-06-09 14:03:52 -04:00
|
|
|
"LEN -- Return the length of the slowlog.",
|
|
|
|
"RESET -- Reset the slowlog.",
|
2017-12-06 06:05:11 -05:00
|
|
|
NULL
|
2017-11-27 10:57:44 -05:00
|
|
|
};
|
|
|
|
addReplyHelp(c, help);
|
|
|
|
} else if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"reset")) {
|
2011-06-30 07:27:32 -04:00
|
|
|
slowlogReset();
|
|
|
|
addReply(c,shared.ok);
|
|
|
|
} else if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"len")) {
|
|
|
|
addReplyLongLong(c,listLength(server.slowlog));
|
|
|
|
} else if ((c->argc == 2 || c->argc == 3) &&
|
|
|
|
!strcasecmp(c->argv[1]->ptr,"get"))
|
|
|
|
{
|
|
|
|
long count = 10, sent = 0;
|
|
|
|
listIter li;
|
|
|
|
void *totentries;
|
|
|
|
listNode *ln;
|
|
|
|
slowlogEntry *se;
|
|
|
|
|
|
|
|
if (c->argc == 3 &&
|
2015-07-26 17:17:55 -04:00
|
|
|
getLongFromObjectOrReply(c,c->argv[2],&count,NULL) != C_OK)
|
2011-06-30 07:27:32 -04:00
|
|
|
return;
|
|
|
|
|
|
|
|
listRewind(server.slowlog,&li);
|
2018-11-26 10:20:01 -05:00
|
|
|
totentries = addReplyDeferredLen(c);
|
2011-06-30 07:27:32 -04:00
|
|
|
while(count-- && (ln = listNext(&li))) {
|
|
|
|
int j;
|
|
|
|
|
|
|
|
se = ln->value;
|
2018-11-26 10:20:01 -05:00
|
|
|
addReplyArrayLen(c,6);
|
2011-06-30 11:36:15 -04:00
|
|
|
addReplyLongLong(c,se->id);
|
2011-06-30 07:27:32 -04:00
|
|
|
addReplyLongLong(c,se->time);
|
|
|
|
addReplyLongLong(c,se->duration);
|
2018-11-26 10:20:01 -05:00
|
|
|
addReplyArrayLen(c,se->argc);
|
2011-06-30 07:27:32 -04:00
|
|
|
for (j = 0; j < se->argc; j++)
|
|
|
|
addReplyBulk(c,se->argv[j]);
|
2017-06-15 06:57:54 -04:00
|
|
|
addReplyBulkCBuffer(c,se->peerid,sdslen(se->peerid));
|
|
|
|
addReplyBulkCBuffer(c,se->cname,sdslen(se->cname));
|
2011-06-30 07:27:32 -04:00
|
|
|
sent++;
|
|
|
|
}
|
2018-11-26 10:20:01 -05:00
|
|
|
setDeferredArrayLen(c,totentries,sent);
|
2011-06-30 07:27:32 -04:00
|
|
|
} else {
|
2018-07-02 12:49:34 -04:00
|
|
|
addReplySubcommandSyntaxError(c);
|
2011-06-30 07:27:32 -04:00
|
|
|
}
|
|
|
|
}
|