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