xref: /redis-3.2.3/src/latency.c (revision 3064e9bd)
1 /* The latency monitor allows to easily observe the sources of latency
2  * in a Redis instance using the LATENCY command. Different latency
3  * sources are monitored, like disk I/O, execution of commands, fork
4  * system call, and so forth.
5  *
6  * ----------------------------------------------------------------------------
7  *
8  * Copyright (c) 2014, Salvatore Sanfilippo <antirez at gmail dot com>
9  * All rights reserved.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions are met:
13  *
14  *   * Redistributions of source code must retain the above copyright notice,
15  *     this list of conditions and the following disclaimer.
16  *   * Redistributions in binary form must reproduce the above copyright
17  *     notice, this list of conditions and the following disclaimer in the
18  *     documentation and/or other materials provided with the distribution.
19  *   * Neither the name of Redis nor the names of its contributors may be used
20  *     to endorse or promote products derived from this software without
21  *     specific prior written permission.
22  *
23  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
24  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
27  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
28  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
29  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
30  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
31  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
32  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
33  * POSSIBILITY OF SUCH DAMAGE.
34  */
35 
36 #include "redis.h"
37 
38 /* Dictionary type for latency events. */
39 int dictStringKeyCompare(void *privdata, const void *key1, const void *key2) {
40     REDIS_NOTUSED(privdata);
41     return strcmp(key1,key2) == 0;
42 }
43 
44 unsigned int dictStringHash(const void *key) {
45     return dictGenHashFunction(key, strlen(key));
46 }
47 
48 void dictVanillaFree(void *privdata, void *val);
49 
50 dictType latencyTimeSeriesDictType = {
51     dictStringHash,             /* hash function */
52     NULL,                       /* key dup */
53     NULL,                       /* val dup */
54     dictStringKeyCompare,       /* key compare */
55     dictVanillaFree,            /* key destructor */
56     dictVanillaFree             /* val destructor */
57 };
58 
59 /* ---------------------------- Latency API --------------------------------- */
60 
61 /* Latency monitor initialization. We just need to create the dictionary
62  * of time series, each time serie is craeted on demand in order to avoid
63  * having a fixed list to maintain. */
64 void latencyMonitorInit(void) {
65     server.latency_events = dictCreate(&latencyTimeSeriesDictType,NULL);
66 }
67 
68 /* Add the specified sample to the specified time series "event".
69  * This function is usually called via latencyAddSampleIfNeeded(), that
70  * is a macro that only adds the sample if the latency is higher than
71  * server.latency_monitor_threshold. */
72 void latencyAddSample(char *event, mstime_t latency) {
73     struct latencyTimeSeries *ts = dictFetchValue(server.latency_events,event);
74     time_t now = time(NULL);
75     int prev;
76 
77     /* Create the time series if it does not exist. */
78     if (ts == NULL) {
79         ts = zmalloc(sizeof(*ts));
80         ts->idx = 0;
81         ts->max = 0;
82         memset(ts->samples,0,sizeof(ts->samples));
83         dictAdd(server.latency_events,zstrdup(event),ts);
84     }
85 
86     /* If the previous sample is in the same second, we update our old sample
87      * if this latency is > of the old one, or just return. */
88     prev = (ts->idx + LATENCY_TS_LEN - 1) % LATENCY_TS_LEN;
89     if (ts->samples[prev].time == now) {
90         if (latency > ts->samples[prev].latency)
91             ts->samples[prev].latency = latency;
92         return;
93     }
94 
95     ts->samples[ts->idx].time = time(NULL);
96     ts->samples[ts->idx].latency = latency;
97     if (latency > ts->max) ts->max = latency;
98 
99     ts->idx++;
100     if (ts->idx == LATENCY_TS_LEN) ts->idx = 0;
101 }
102 
103 /* Reset data for the specified event, or all the events data if 'event' is
104  * NULL.
105  *
106  * Note: this is O(N) even when event_to_reset is not NULL because makes
107  * the code simpler and we have a small fixed max number of events. */
108 int latencyResetEvent(char *event_to_reset) {
109     dictIterator *di;
110     dictEntry *de;
111     int resets = 0;
112 
113     di = dictGetSafeIterator(server.latency_events);
114     while((de = dictNext(di)) != NULL) {
115         char *event = dictGetKey(de);
116 
117         if (event_to_reset == NULL || strcasecmp(event,event_to_reset) == 0) {
118             dictDelete(server.latency_events, event);
119             resets++;
120         }
121     }
122     dictReleaseIterator(di);
123     return resets;
124 }
125 
126 /* ------------------------ Latency reporting (doctor) ---------------------- */
127 
128 /* Analyze the samples avaialble for a given event and return a structure
129  * populate with different metrics, average, MAD, min, max, and so forth.
130  * Check latency.h definition of struct latenctStat for more info.
131  * If the specified event has no elements the structure is populate with
132  * zero values. */
133 void analyzeLatencyForEvent(char *event, struct latencyStats *ls) {
134     struct latencyTimeSeries *ts = dictFetchValue(server.latency_events,event);
135     int j;
136     uint64_t sum;
137 
138     ls->all_time_high = ts ? ts->max : 0;
139     ls->avg = 0;
140     ls->min = 0;
141     ls->max = 0;
142     ls->mad = 0;
143     ls->samples = 0;
144     ls->period = 0;
145     if (!ts) return;
146 
147     /* First pass, populate everything but the MAD. */
148     sum = 0;
149     for (j = 0; j < LATENCY_TS_LEN; j++) {
150         if (ts->samples[j].time == 0) continue;
151         ls->samples++;
152         if (ls->samples == 1) {
153             ls->min = ls->max = ts->samples[j].latency;
154         } else {
155             if (ls->min > ts->samples[j].latency)
156                 ls->min = ts->samples[j].latency;
157             if (ls->max < ts->samples[j].latency)
158                 ls->max = ts->samples[j].latency;
159         }
160         sum += ts->samples[j].latency;
161 
162         /* Track the oldest event time in ls->period. */
163         if (ls->period == 0 || ts->samples[j].time < ls->period)
164             ls->period = ts->samples[j].time;
165     }
166 
167     /* So far avg is actually the sum of the latencies, and period is
168      * the oldest event time. We need to make the first an average and
169      * the second a range of seconds. */
170     if (ls->samples) {
171         ls->avg = sum / ls->samples;
172         ls->period = time(NULL) - ls->period;
173         if (ls->period == 0) ls->period = 1;
174     }
175 
176     /* Second pass, compute MAD. */
177     sum = 0;
178     for (j = 0; j < LATENCY_TS_LEN; j++) {
179         int64_t delta;
180 
181         if (ts->samples[j].time == 0) continue;
182         delta = (int64_t)ls->avg - ts->samples[j].latency;
183         if (delta < 0) delta = -delta;
184         sum += delta;
185     }
186     if (ls->samples) ls->mad = sum / ls->samples;
187 }
188 
189 /* Create a human readable report of latency events for this Redis instance. */
190 sds createLatencyReport(void) {
191     sds report = sdsempty();
192     int advise_better_vm = 0;       /* Better virtual machines. */
193     int advise_slowlog_enabled = 0; /* Enable slowlog. */
194     int advise_slowlog_tuning = 0;  /* Reconfigure slowlog. */
195     int advise_slowlog_inspect = 0; /* Check your slowlog. */
196     int advise_disk_contention = 0; /* Try to lower disk contention. */
197     int advise_scheduler = 0;       /* Intrinsic latency. */
198     int advise_data_writeback = 0;  /* data=writeback. */
199     int advise_no_appendfsync = 0;  /* don't fsync during rewrites. */
200     int advise_local_disk = 0;      /* Avoid remote disks. */
201     int advise_ssd = 0;             /* Use an SSD drive. */
202     int advise_write_load_info = 0; /* Print info about AOF and write load. */
203     int advise_hz = 0;              /* Use higher HZ. */
204     int advise_large_objects = 0;   /* Deletion of large objects. */
205     int advise_relax_fsync_policy = 0; /* appendfsync always is slow. */
206     int advices = 0;
207 
208     /* Return ASAP if the latency engine is disabled and it looks like it
209      * was never enabled so far. */
210     if (dictSize(server.latency_events) == 0 &&
211         server.latency_monitor_threshold == 0)
212     {
213         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");
214         return report;
215     }
216 
217     /* Show all the events stats and add for each event some event-related
218      * comment depending on the values. */
219     dictIterator *di;
220     dictEntry *de;
221     int eventnum = 0;
222 
223     di = dictGetIterator(server.latency_events);
224     while((de = dictNext(di)) != NULL) {
225         char *event = dictGetKey(de);
226         struct latencyTimeSeries *ts = dictGetVal(de);
227         struct latencyStats ls;
228 
229         if (ts == NULL) continue;
230         eventnum++;
231         if (eventnum == 1) {
232             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");
233         }
234         analyzeLatencyForEvent(event,&ls);
235 
236         report = sdscatprintf(report,
237             "%d. %s: %d latency spikes (average %lums, mean deviation %lums, period %.2f sec). Worst all time event %lums.",
238             eventnum, event,
239             ls.samples,
240             (unsigned long) ls.avg,
241             (unsigned long) ls.mad,
242             (double) ls.period/ls.samples,
243             (unsigned long) ts->max);
244 
245         /* Fork */
246         if (!strcasecmp(event,"fork")) {
247             char *fork_quality;
248             if (server.stat_fork_rate < 10) {
249                 fork_quality = "terrible";
250                 advise_better_vm = 1;
251                 advices++;
252             } else if (server.stat_fork_rate < 25) {
253                 fork_quality = "poor";
254                 advise_better_vm = 1;
255                 advices++;
256             } else if (server.stat_fork_rate < 100) {
257                 fork_quality = "good";
258             } else {
259                 fork_quality = "excellent";
260             }
261             report = sdscatprintf(report,
262                 " Fork rate is %.2f GB/sec (%s).", server.stat_fork_rate,
263                 fork_quality);
264         }
265 
266         /* Potentially commands. */
267         if (!strcasecmp(event,"command")) {
268             if (server.slowlog_log_slower_than == 0) {
269                 advise_slowlog_enabled = 1;
270                 advices++;
271             } else if (server.slowlog_log_slower_than/1000 >
272                        server.latency_monitor_threshold)
273             {
274                 advise_slowlog_tuning = 1;
275                 advices++;
276             }
277             advise_slowlog_inspect = 1;
278             advise_large_objects = 1;
279             advices += 2;
280         }
281 
282         /* fast-command. */
283         if (!strcasecmp(event,"fast-command")) {
284             advise_scheduler = 1;
285             advices++;
286         }
287 
288         /* AOF and I/O. */
289         if (!strcasecmp(event,"aof-write-pending-fsync")) {
290             advise_local_disk = 1;
291             advise_disk_contention = 1;
292             advise_ssd = 1;
293             advise_data_writeback = 1;
294             advices += 4;
295         }
296 
297         if (!strcasecmp(event,"aof-write-active-child")) {
298             advise_no_appendfsync = 1;
299             advise_data_writeback = 1;
300             advise_ssd = 1;
301             advices += 3;
302         }
303 
304         if (!strcasecmp(event,"aof-write-alone")) {
305             advise_local_disk = 1;
306             advise_data_writeback = 1;
307             advise_ssd = 1;
308             advices += 3;
309         }
310 
311         if (!strcasecmp(event,"aof-fsync-always")) {
312             advise_relax_fsync_policy = 1;
313             advices++;
314         }
315 
316         if (!strcasecmp(event,"aof-fstat") ||
317             !strcasecmp(event,"rdb-unlik-temp-file")) {
318             advise_disk_contention = 1;
319             advise_local_disk = 1;
320             advices += 2;
321         }
322 
323         if (!strcasecmp(event,"aof-rewrite-diff-write") ||
324             !strcasecmp(event,"aof-rename")) {
325             advise_write_load_info = 1;
326             advise_data_writeback = 1;
327             advise_ssd = 1;
328             advise_local_disk = 1;
329             advices += 4;
330         }
331 
332         /* Expire cycle. */
333         if (!strcasecmp(event,"expire-cycle")) {
334             advise_hz = 1;
335             advise_large_objects = 1;
336             advices += 2;
337         }
338 
339         /* Eviction cycle. */
340         if (!strcasecmp(event,"eviction-cycle")) {
341             advise_large_objects = 1;
342             advices++;
343         }
344 
345         report = sdscatlen(report,"\n",1);
346     }
347     dictReleaseIterator(di);
348 
349     if (eventnum == 0) {
350         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");
351     } else if (advices == 0) {
352         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");
353     } else {
354         /* Add all the suggestions accumulated so far. */
355 
356         /* Better VM. */
357         report = sdscat(report,"\nI have a few advices for you:\n\n");
358         if (advise_better_vm) {
359             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");
360         }
361 
362         /* Slow log. */
363         if (advise_slowlog_enabled) {
364             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);
365         }
366 
367         if (advise_slowlog_tuning) {
368             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);
369         }
370 
371         if (advise_slowlog_inspect) {
372             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");
373         }
374 
375         /* Intrinsic latency. */
376         if (advise_scheduler) {
377             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"
378             "  1) Lower the system load.\n"
379             "  2) Use a computer / VM just for Redis if you are running other softawre in the same system.\n"
380             "  3) Check if you have a \"noisy neighbour\" problem.\n"
381             "  4) Check with 'redis-cli --intrinsic-latency 100' what is the intrinsic latency in your system.\n"
382             "  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");
383         }
384 
385         /* AOF / Disk latency. */
386         if (advise_local_disk) {
387             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");
388         }
389 
390         if (advise_ssd) {
391             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");
392         }
393 
394         if (advise_data_writeback) {
395             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");
396         }
397 
398         if (advise_disk_contention) {
399             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");
400         }
401 
402         if (advise_no_appendfsync) {
403             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");
404         }
405 
406         if (advise_relax_fsync_policy && server.aof_fsync == AOF_FSYNC_ALWAYS) {
407             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");
408         }
409 
410         if (advise_write_load_info) {
411             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");
412         }
413 
414         if (advise_hz && server.hz < 100) {
415             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");
416         }
417 
418         if (advise_large_objects) {
419             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");
420         }
421     }
422 
423     return report;
424 }
425 
426 /* ---------------------- Latency command implementation -------------------- */
427 
428 /* latencyCommand() helper to produce a time-delay reply for all the samples
429  * in memory for the specified time series. */
430 void latencyCommandReplyWithSamples(redisClient *c, struct latencyTimeSeries *ts) {
431     void *replylen = addDeferredMultiBulkLength(c);
432     int samples = 0, j;
433 
434     for (j = 0; j < LATENCY_TS_LEN; j++) {
435         int i = (ts->idx + j) % LATENCY_TS_LEN;
436 
437         if (ts->samples[i].time == 0) continue;
438         addReplyMultiBulkLen(c,2);
439         addReplyLongLong(c,ts->samples[i].time);
440         addReplyLongLong(c,ts->samples[i].latency);
441         samples++;
442     }
443     setDeferredMultiBulkLength(c,replylen,samples);
444 }
445 
446 /* latencyCommand() helper to produce the reply for the LATEST subcommand,
447  * listing the last latency sample for every event type registered so far. */
448 void latencyCommandReplyWithLatestEvents(redisClient *c) {
449     dictIterator *di;
450     dictEntry *de;
451 
452     addReplyMultiBulkLen(c,dictSize(server.latency_events));
453     di = dictGetIterator(server.latency_events);
454     while((de = dictNext(di)) != NULL) {
455         char *event = dictGetKey(de);
456         struct latencyTimeSeries *ts = dictGetVal(de);
457         int last = (ts->idx + LATENCY_TS_LEN - 1) % LATENCY_TS_LEN;
458 
459         addReplyMultiBulkLen(c,4);
460         addReplyBulkCString(c,event);
461         addReplyLongLong(c,ts->samples[last].time);
462         addReplyLongLong(c,ts->samples[last].latency);
463         addReplyLongLong(c,ts->max);
464     }
465     dictReleaseIterator(di);
466 }
467 
468 #define LATENCY_GRAPH_COLS 80
469 sds latencyCommandGenSparkeline(char *event, struct latencyTimeSeries *ts) {
470     int j;
471     struct sequence *seq = createSparklineSequence();
472     sds graph = sdsempty();
473     uint32_t min = 0, max = 0;
474 
475     for (j = 0; j < LATENCY_TS_LEN; j++) {
476         int i = (ts->idx + j) % LATENCY_TS_LEN;
477         int elapsed;
478         char *label;
479         char buf[64];
480 
481         if (ts->samples[i].time == 0) continue;
482         /* Update min and max. */
483         if (seq->length == 0) {
484             min = max = ts->samples[i].latency;
485         } else {
486             if (ts->samples[i].latency > max) max = ts->samples[i].latency;
487             if (ts->samples[i].latency < min) min = ts->samples[i].latency;
488         }
489         /* Use as label the number of seconds / minutes / hours / days
490          * ago the event happened. */
491         elapsed = time(NULL) - ts->samples[i].time;
492         if (elapsed < 60)
493             snprintf(buf,sizeof(buf),"%ds",elapsed);
494         else if (elapsed < 3600)
495             snprintf(buf,sizeof(buf),"%dm",elapsed/60);
496         else if (elapsed < 3600*24)
497             snprintf(buf,sizeof(buf),"%dh",elapsed/3600);
498         else
499             snprintf(buf,sizeof(buf),"%dd",elapsed/(3600*24));
500         label = zstrdup(buf);
501         sparklineSequenceAddSample(seq,ts->samples[i].latency,label);
502     }
503 
504     graph = sdscatprintf(graph,
505         "%s - high %lu ms, low %lu ms (all time high %lu ms)\n", event,
506         (unsigned long) max, (unsigned long) min, (unsigned long) ts->max);
507     for (j = 0; j < LATENCY_GRAPH_COLS; j++)
508         graph = sdscatlen(graph,"-",1);
509     graph = sdscatlen(graph,"\n",1);
510     graph = sparklineRender(graph,seq,LATENCY_GRAPH_COLS,4,SPARKLINE_FILL);
511     freeSparklineSequence(seq);
512     return graph;
513 }
514 
515 /* LATENCY command implementations.
516  *
517  * LATENCY SAMPLES: return time-latency samples for the specified event.
518  * LATENCY LATEST: return the latest latency for all the events classes.
519  * LATENCY DOCTOR: returns an human readable analysis of instance latency.
520  * LATENCY GRAPH: provide an ASCII graph of the latency of the specified event.
521  */
522 void latencyCommand(redisClient *c) {
523     struct latencyTimeSeries *ts;
524 
525     if (!strcasecmp(c->argv[1]->ptr,"history") && c->argc == 3) {
526         /* LATENCY HISTORY <event> */
527         ts = dictFetchValue(server.latency_events,c->argv[2]->ptr);
528         if (ts == NULL) {
529             addReplyMultiBulkLen(c,0);
530         } else {
531             latencyCommandReplyWithSamples(c,ts);
532         }
533     } else if (!strcasecmp(c->argv[1]->ptr,"graph") && c->argc == 3) {
534         /* LATENCY GRAPH <event> */
535         sds graph;
536         dictEntry *de;
537         char *event;
538 
539         de = dictFind(server.latency_events,c->argv[2]->ptr);
540         if (de == NULL) goto nodataerr;
541         ts = dictGetVal(de);
542         event = dictGetKey(de);
543 
544         graph = latencyCommandGenSparkeline(event,ts);
545         addReplyBulkCString(c,graph);
546         sdsfree(graph);
547     } else if (!strcasecmp(c->argv[1]->ptr,"latest") && c->argc == 2) {
548         /* LATENCY LATEST */
549         latencyCommandReplyWithLatestEvents(c);
550     } else if (!strcasecmp(c->argv[1]->ptr,"doctor") && c->argc == 2) {
551         /* LATENCY DOCTOR */
552         sds report = createLatencyReport();
553 
554         addReplyBulkCBuffer(c,report,sdslen(report));
555         sdsfree(report);
556     } else if (!strcasecmp(c->argv[1]->ptr,"reset") && c->argc >= 2) {
557         /* LATENCY RESET */
558         if (c->argc == 2) {
559             addReplyLongLong(c,latencyResetEvent(NULL));
560         } else {
561             int j, resets = 0;
562 
563             for (j = 2; j < c->argc; j++)
564                 resets += latencyResetEvent(c->argv[j]->ptr);
565             addReplyLongLong(c,resets);
566         }
567     } else {
568         addReply(c,shared.syntaxerr);
569     }
570     return;
571 
572 nodataerr:
573     /* Common error when the user asks for an event we have no latency
574      * information about. */
575     addReplyErrorFormat(c,
576         "No samples available for event '%s'", (char*) c->argv[2]->ptr);
577 }
578 
579