From aaa31b7ab58eaefbb4a0fa9919bd1d6766e44a5e Mon Sep 17 00:00:00 2001 From: Pavel Rochnyack Date: Mon, 18 Jun 2018 20:49:29 +0700 Subject: [PATCH] redis plugin: Added commands statistics reporting --- src/collectd.conf.pod | 7 ++++ src/redis.c | 99 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/types.db | 2 ++ 3 files changed, 108 insertions(+) diff --git a/src/collectd.conf.pod b/src/collectd.conf.pod index 093ff765..34a3ed7d 100644 --- a/src/collectd.conf.pod +++ b/src/collectd.conf.pod @@ -7292,6 +7292,7 @@ Server state is requested by sending I query. Host "localhost" Port "6379" Timeout 2000 + ReportCommandStats false #Database 0 Type "queue_length" @@ -7335,6 +7336,12 @@ globally. Defaults to 2000 (2 seconds). +=item B B|B + +Enables or disables reporting of statistics based on the command type, including +rate of command calls and average CPU time consumed. +Defaults to B. + =item B I The B block identifies a query to execute against the redis server. diff --git a/src/redis.c b/src/redis.c index 2a355045..15d7319b 100644 --- a/src/redis.c +++ b/src/redis.c @@ -67,6 +67,7 @@ struct redis_node_s { char *passwd; int port; struct timeval timeout; + bool report_command_stats; redisContext *redisContext; redis_query_t *queries; @@ -220,6 +221,8 @@ static int redis_config_node(oconfig_item_t *ci) /* {{{ */ } } else if (strcasecmp("Password", option->key) == 0) status = cf_util_get_string(option, &rn->passwd); + else if (strcasecmp("ReportCommandStats", option->key) == 0) + status = cf_util_get_boolean(option, &rn->report_command_stats); else WARNING("redis plugin: Option `%s' not allowed inside a `Node' " "block. I'll ignore this option.", @@ -616,6 +619,95 @@ static void redis_read_server_info(redis_node_t *rn) { freeReplyObject(rr); } /* void redis_read_server_info */ +static void redis_read_command_stats(redis_node_t *rn) { + redisReply *rr; + + if ((rr = c_redisCommand(rn, "INFO commandstats")) == NULL) { + WARNING("redis plugin: node `%s': unable to get `INFO commandstats'.", + rn->name); + return; + } + + if (rr->type != REDIS_REPLY_STRING) { + WARNING("redis plugin: node `%s' `INFO commandstats' returned unsupported " + "redis type %i.", + rn->name, rr->type); + freeReplyObject(rr); + return; + } + + char command[DATA_MAX_NAME_LEN]; + char *line; + char *ptr = rr->str; + char *saveptr = NULL; + while ((line = strtok_r(ptr, "\n\r", &saveptr)) != NULL) { + ptr = NULL; + + if (line[0] == '#') + continue; + + /* command name */ + if (strstr(line, "cmdstat_") != line) { + ERROR("redis plugin: not found 'cmdstat_' prefix in line '%s'", line); + continue; + } + + char *values = strstr(line, ":"); + if (values == NULL) { + ERROR("redis plugin: not found ':' separator in line '%s'", line); + continue; + } + + int cmd_len = values - line - strlen("cmdstat_") + 1; + if (cmd_len > DATA_MAX_NAME_LEN) + cmd_len = DATA_MAX_NAME_LEN; + + sstrncpy(command, line + strlen("cmdstat_"), cmd_len); + + /* parse values */ + /* cmdstat_publish:calls=20795774,usec=111039258,usec_per_call=5.34 */ + + char *field; + char *saveptr_line = NULL; + values++; + while ((field = strtok_r(values, "=,", &saveptr_line)) != NULL) { + values = NULL; + + if ((strcmp(field, "calls") == 0) && + ((field = strtok_r(NULL, "=,", &saveptr_line)) != NULL)) { + + char *endptr = NULL; + errno = 0; + derive_t calls = strtoll(field, &endptr, 0); + + if ((endptr == field) || (errno != 0)) + continue; + + ERROR("redis plugin: Found CALLS value %lld (%s)", calls, command); + redis_submit(rn->name, "commands", command, (value_t){.derive = calls}); + continue; + } + + if ((strcmp(field, "usec") == 0) && + ((field = strtok_r(NULL, "=,", &saveptr_line)) != NULL)) { + + char *endptr = NULL; + errno = 0; + derive_t calls = strtoll(field, &endptr, 0); + + if ((endptr == field) || (errno != 0)) + continue; + + ERROR("redis plugin: Found USEC value %lld (%s)", calls, command); + redis_submit(rn->name, "redis_command_cputime", command, + (value_t){.derive = calls}); + continue; + } + } + } + freeReplyObject(rr); +} /* void redis_read_command_stats */ + static int redis_read(user_data_t *user_data) /* {{{ */ { redis_node_t *rn = user_data->data; @@ -633,6 +725,13 @@ static int redis_read(user_data_t *user_data) /* {{{ */ if (!rn->redisContext) /* connection lost */ return -1; + if (rn->report_command_stats) { + redis_read_command_stats(rn); + + if (!rn->redisContext) /* connection lost */ + return -1; + } + for (redis_query_t *rq = rn->queries; rq != NULL; rq = rq->next) { redis_handle_query(rn, rq); if (!rn->redisContext) /* connection lost */ diff --git a/src/types.db b/src/types.db index 1b1e6f0c..f4933ee3 100644 --- a/src/types.db +++ b/src/types.db @@ -31,6 +31,7 @@ clock_state value:GAUGE:0:U clock_stratum value:GAUGE:0:U compression uncompressed:DERIVE:0:U, compressed:DERIVE:0:U compression_ratio value:GAUGE:0:2 +commands value:DERIVE:0:U connections value:DERIVE:0:U conntrack value:GAUGE:0:4294967295 contextswitch value:DERIVE:0:U @@ -211,6 +212,7 @@ ps_vm value:GAUGE:0:9223372036854775807 pubsub value:GAUGE:0:U queue_length value:GAUGE:0:U records value:GAUGE:0:U +redis_command_cputime value:DERIVE:0:U requests value:GAUGE:0:U response_code value:GAUGE:0:U response_time value:GAUGE:0:U -- 2.11.0