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. */ |
48 | slowlogEntry *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. */ |
100 | void 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. */ |
114 | void 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. */ |
123 | void 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. */ |
135 | void 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. */ |
142 | void 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." , |
154 | NULL |
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 | |