1/* Slowlog implements a system that is able to remember the latest N
2 * queries that took more than M microseconds to execute.
3 *
4 * The execution time to reach to be logged in the slow log is set
5 * using the 'slowlog-log-slower-than' config directive, that is also
6 * readable and writable using the CONFIG SET/GET command.
7 *
8 * The slow queries log is actually not "logged" in the Redis log file
9 * but is accessible thanks to the SLOWLOG command.
10 *
11 * ----------------------------------------------------------------------------
12 *
13 * Copyright (c) 2009-2012, Salvatore Sanfilippo <antirez at gmail dot com>
14 * All rights reserved.
15 *
16 * Redistribution and use in source and binary forms, with or without
17 * modification, are permitted provided that the following conditions are met:
18 *
19 * * Redistributions of source code must retain the above copyright notice,
20 * this list of conditions and the following disclaimer.
21 * * Redistributions in binary form must reproduce the above copyright
22 * notice, this list of conditions and the following disclaimer in the
23 * documentation and/or other materials provided with the distribution.
24 * * Neither the name of Redis nor the names of its contributors may be used
25 * to endorse or promote products derived from this software without
26 * specific prior written permission.
27 *
28 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
29 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
30 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
31 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
32 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
33 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
34 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
35 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
36 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
37 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
38 * POSSIBILITY OF SUCH DAMAGE.
39 */
40
41
42#include "server.h"
43#include "slowlog.h"
44
45/* Create a new slowlog entry.
46 * Incrementing the ref count of all the objects retained is up to
47 * this function. */
48slowlogEntry *slowlogCreateEntry(client *c, robj **argv, int argc, long long duration) {
49 slowlogEntry *se = zmalloc(sizeof(*se));
50 int j, slargc = argc;
51
52 if (slargc > SLOWLOG_ENTRY_MAX_ARGC) slargc = SLOWLOG_ENTRY_MAX_ARGC;
53 se->argc = slargc;
54 se->argv = zmalloc(sizeof(robj*)*slargc);
55 for (j = 0; j < slargc; j++) {
56 /* Logging too many arguments is a useless memory waste, so we stop
57 * at SLOWLOG_ENTRY_MAX_ARGC, but use the last argument to specify
58 * how many remaining arguments there were in the original command. */
59 if (slargc != argc && j == slargc-1) {
60 se->argv[j] = createObject(OBJ_STRING,
61 sdscatprintf(sdsempty(),"... (%d more arguments)",
62 argc-slargc+1));
63 } else {
64 /* Trim too long strings as well... */
65 if (argv[j]->type == OBJ_STRING &&
66 sdsEncodedObject(argv[j]) &&
67 sdslen(argv[j]->ptr) > SLOWLOG_ENTRY_MAX_STRING)
68 {
69 sds s = sdsnewlen(argv[j]->ptr, SLOWLOG_ENTRY_MAX_STRING);
70
71 s = sdscatprintf(s,"... (%lu more bytes)",
72 (unsigned long)
73 sdslen(argv[j]->ptr) - SLOWLOG_ENTRY_MAX_STRING);
74 se->argv[j] = createObject(OBJ_STRING,s);
75 } else if (argv[j]->refcount == OBJ_SHARED_REFCOUNT) {
76 se->argv[j] = argv[j];
77 } else {
78 /* Here we need to duplicate the string objects composing the
79 * argument vector of the command, because those may otherwise
80 * end shared with string objects stored into keys. Having
81 * shared objects between any part of Redis, and the data
82 * structure holding the data, is a problem: FLUSHALL ASYNC
83 * may release the shared string object and create a race. */
84 se->argv[j] = dupStringObject(argv[j]);
85 }
86 }
87 }
88 se->time = time(NULL);
89 se->duration = duration;
90 se->id = server.slowlog_entry_id++;
91 se->peerid = sdsnew(getClientPeerId(c));
92 se->cname = c->name ? sdsnew(c->name->ptr) : sdsempty();
93 return se;
94}
95
96/* Free a slow log entry. The argument is void so that the prototype of this
97 * function matches the one of the 'free' method of adlist.c.
98 *
99 * This function will take care to release all the retained object. */
100void slowlogFreeEntry(void *septr) {
101 slowlogEntry *se = septr;
102 int j;
103
104 for (j = 0; j < se->argc; j++)
105 decrRefCount(se->argv[j]);
106 zfree(se->argv);
107 sdsfree(se->peerid);
108 sdsfree(se->cname);
109 zfree(se);
110}
111
112/* Initialize the slow log. This function should be called a single time
113 * at server startup. */
114void slowlogInit(void) {
115 server.slowlog = listCreate();
116 server.slowlog_entry_id = 0;
117 listSetFreeMethod(server.slowlog,slowlogFreeEntry);
118}
119
120/* Push a new entry into the slow log.
121 * This function will make sure to trim the slow log accordingly to the
122 * configured max length. */
123void slowlogPushEntryIfNeeded(client *c, robj **argv, int argc, long long duration) {
124 if (server.slowlog_log_slower_than < 0) return; /* Slowlog disabled */
125 if (duration >= server.slowlog_log_slower_than)
126 listAddNodeHead(server.slowlog,
127 slowlogCreateEntry(c,argv,argc,duration));
128
129 /* Remove old entries if needed. */
130 while (listLength(server.slowlog) > server.slowlog_max_len)
131 listDelNode(server.slowlog,listLast(server.slowlog));
132}
133
134/* Remove all the entries from the current slow log. */
135void slowlogReset(void) {
136 while (listLength(server.slowlog) > 0)
137 listDelNode(server.slowlog,listLast(server.slowlog));
138}
139
140/* The SLOWLOG command. Implements all the subcommands needed to handle the
141 * Redis slow log. */
142void slowlogCommand(client *c) {
143 if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"help")) {
144 const char *help[] = {
145"GET [<count>]",
146" Return top <count> entries from the slowlog (default: 10, -1 mean all).",
147" Entries are made of:",
148" id, timestamp, time in microseconds, arguments array, client IP and port,",
149" client name",
150"LEN",
151" Return the length of the slowlog.",
152"RESET",
153" Reset the slowlog.",
154NULL
155 };
156 addReplyHelp(c, help);
157 } else if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"reset")) {
158 slowlogReset();
159 addReply(c,shared.ok);
160 } else if (c->argc == 2 && !strcasecmp(c->argv[1]->ptr,"len")) {
161 addReplyLongLong(c,listLength(server.slowlog));
162 } else if ((c->argc == 2 || c->argc == 3) &&
163 !strcasecmp(c->argv[1]->ptr,"get"))
164 {
165 long count = 10, sent = 0;
166 listIter li;
167 void *totentries;
168 listNode *ln;
169 slowlogEntry *se;
170
171 if (c->argc == 3) {
172 /* Consume count arg. */
173 if (getRangeLongFromObjectOrReply(c, c->argv[2], -1,
174 LONG_MAX, &count, "count should be greater than or equal to -1") != C_OK)
175 return;
176
177 if (count == -1) {
178 /* We treat -1 as a special value, which means to get all slow logs.
179 * Simply set count to the length of server.slowlog.*/
180 count = listLength(server.slowlog);
181 }
182 }
183
184 listRewind(server.slowlog,&li);
185 totentries = addReplyDeferredLen(c);
186 while(count-- && (ln = listNext(&li))) {
187 int j;
188
189 se = ln->value;
190 addReplyArrayLen(c,6);
191 addReplyLongLong(c,se->id);
192 addReplyLongLong(c,se->time);
193 addReplyLongLong(c,se->duration);
194 addReplyArrayLen(c,se->argc);
195 for (j = 0; j < se->argc; j++)
196 addReplyBulk(c,se->argv[j]);
197 addReplyBulkCBuffer(c,se->peerid,sdslen(se->peerid));
198 addReplyBulkCBuffer(c,se->cname,sdslen(se->cname));
199 sent++;
200 }
201 setDeferredArrayLen(c,totentries,sent);
202 } else {
203 addReplySubcommandSyntaxError(c);
204 }
205}
206