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