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