From 484cccc49ede0f959c6b652c58b246312f9fe668 Mon Sep 17 00:00:00 2001 From: Adam Flott Date: Thu, 30 May 2019 10:40:29 -0400 Subject: [PATCH 1/2] Capture and parse the cmdstat timings from `INFO ALL` command --- plugins/inputs/redis/README.md | 8 +++++ plugins/inputs/redis/redis.go | 48 +++++++++++++++++++++++++++++- plugins/inputs/redis/redis_test.go | 20 +++++++++++++ 3 files changed, 75 insertions(+), 1 deletion(-) diff --git a/plugins/inputs/redis/README.md b/plugins/inputs/redis/README.md index 79122f22851f7..5b3cbebc2060e 100644 --- a/plugins/inputs/redis/README.md +++ b/plugins/inputs/redis/README.md @@ -130,6 +130,9 @@ Additionally the plugin also calculates the hit/miss ratio (keyspace\_hitrate) a - The redis_keyspace measurement has an additional database tag: - database +- The redis_cmdstat measurement has an additional tag: + - command + ### Example Output: Using this configuration: @@ -161,3 +164,8 @@ redis_keyspace: ``` > redis_keyspace,database=db1,host=host,server=localhost,port=6379,replication_role=master keys=1i,expires=0i,avg_ttl=0i 1493101350000000000 ``` + +redis_command: +``` +> redis_cmdstat,command=publish,host=host,port=6379,replication_role=master,server=localhost calls=68113i,usec=325146i,usec_per_call=4.77 1559227136000000000 +``` diff --git a/plugins/inputs/redis/redis.go b/plugins/inputs/redis/redis.go index cd438397c2318..af7dbe585079b 100644 --- a/plugins/inputs/redis/redis.go +++ b/plugins/inputs/redis/redis.go @@ -37,7 +37,7 @@ type RedisClient struct { } func (r *RedisClient) Info() *redis.StringCmd { - return r.client.Info() + return r.client.Info("ALL") } func (r *RedisClient) BaseTags() map[string]string { @@ -248,6 +248,11 @@ func gatherInfoOutput( gatherKeyspaceLine(name, kline, acc, tags) continue } + if section == "Commandstats" { + kline := strings.TrimSpace(parts[1]) + gatherCommandstateLine(name, kline, acc, tags) + continue + } metric = name } @@ -321,6 +326,47 @@ func gatherKeyspaceLine( } } +// Parse the special cmdstat lines. +// Example: +// cmdstat_publish:calls=33791,usec=208789,usec_per_call=6.18 +// Tag: cmdstat=publish; Fields: calls=33791i,usec=208789i,usec_per_call=6.18 +func gatherCommandstateLine( + name string, + line string, + acc telegraf.Accumulator, + global_tags map[string]string, +) { + if strings.HasPrefix(name, "cmdstat") { + fields := make(map[string]interface{}) + tags := make(map[string]string) + for k, v := range global_tags { + tags[k] = v + } + tags["command"] = strings.TrimPrefix(name, "cmdstat_") + parts := strings.Split(line, ",") + for _, part := range parts { + kv := strings.Split(part, "=") + if len(kv) == 2 { + switch kv[0] { + case "calls": + fallthrough + case "usec": + ival, err := strconv.ParseInt(kv[1], 10, 64) + if err == nil { + fields[kv[0]] = ival + } + case "usec_per_call": + fval, err := strconv.ParseFloat(kv[1], 64) + if err == nil { + fields[kv[0]] = fval + } + } + } + } + acc.AddFields("redis_cmdstat", fields, tags) + } +} + func init() { inputs.Add("redis", func() telegraf.Input { return &Redis{} diff --git a/plugins/inputs/redis/redis_test.go b/plugins/inputs/redis/redis_test.go index fd16bbdd91eb4..4cf588763f07d 100644 --- a/plugins/inputs/redis/redis_test.go +++ b/plugins/inputs/redis/redis_test.go @@ -116,6 +116,22 @@ func TestRedis_ParseMetrics(t *testing.T) { } acc.AssertContainsTaggedFields(t, "redis", fields, tags) acc.AssertContainsTaggedFields(t, "redis_keyspace", keyspaceFields, keyspaceTags) + + cmdstatSetTags := map[string]string{"host": "redis.net", "replication_role": "master", "command": "set"} + cmdstatSetFields := map[string]interface{}{ + "calls": int64(261265), + "usec": int64(1634157), + "usec_per_call": float64(6.25), + } + acc.AssertContainsTaggedFields(t, "redis_cmdstat", cmdstatSetFields, cmdstatSetTags) + + cmdstatCommandTags := map[string]string{"host": "redis.net", "replication_role": "master", "command": "command"} + cmdstatCommandFields := map[string]interface{}{ + "calls": int64(1), + "usec": int64(990), + "usec_per_call": float64(990.0), + } + acc.AssertContainsTaggedFields(t, "redis_cmdstat", cmdstatCommandFields, cmdstatCommandTags) } const testOutput = `# Server @@ -205,6 +221,10 @@ used_cpu_user:0.05 used_cpu_sys_children:0.00 used_cpu_user_children:0.00 +# Commandstats +cmdstat_set:calls=261265,usec=1634157,usec_per_call=6.25 +cmdstat_command:calls=1,usec=990,usec_per_call=990.00 + # Keyspace db0:keys=2,expires=0,avg_ttl=0 From 15bc9dab788e75d7bff83eb5e195a8bce75bd69c Mon Sep 17 00:00:00 2001 From: Adam Flott Date: Wed, 14 Aug 2019 20:50:35 -0400 Subject: [PATCH 2/2] Redis: Reduce indentation and document cmdstat fields --- plugins/inputs/redis/README.md | 7 +++++ plugins/inputs/redis/redis.go | 54 ++++++++++++++++++---------------- 2 files changed, 36 insertions(+), 25 deletions(-) diff --git a/plugins/inputs/redis/README.md b/plugins/inputs/redis/README.md index 5b3cbebc2060e..38d80b591f0df 100644 --- a/plugins/inputs/redis/README.md +++ b/plugins/inputs/redis/README.md @@ -120,6 +120,13 @@ Additionally the plugin also calculates the hit/miss ratio (keyspace\_hitrate) a - expires(int, number) - avg_ttl(int, number) +- redis_cmdstat + Every Redis used command will have 3 new fields: + - calls(int, number) + - usec(int, mircoseconds) + - usec_per_call(float, microseconds) + + ### Tags: - All measurements have the following tags: diff --git a/plugins/inputs/redis/redis.go b/plugins/inputs/redis/redis.go index af7dbe585079b..47b876e38e205 100644 --- a/plugins/inputs/redis/redis.go +++ b/plugins/inputs/redis/redis.go @@ -336,35 +336,39 @@ func gatherCommandstateLine( acc telegraf.Accumulator, global_tags map[string]string, ) { - if strings.HasPrefix(name, "cmdstat") { - fields := make(map[string]interface{}) - tags := make(map[string]string) - for k, v := range global_tags { - tags[k] = v + if !strings.HasPrefix(name, "cmdstat") { + return + } + + fields := make(map[string]interface{}) + tags := make(map[string]string) + for k, v := range global_tags { + tags[k] = v + } + tags["command"] = strings.TrimPrefix(name, "cmdstat_") + parts := strings.Split(line, ",") + for _, part := range parts { + kv := strings.Split(part, "=") + if len(kv) != 2 { + continue } - tags["command"] = strings.TrimPrefix(name, "cmdstat_") - parts := strings.Split(line, ",") - for _, part := range parts { - kv := strings.Split(part, "=") - if len(kv) == 2 { - switch kv[0] { - case "calls": - fallthrough - case "usec": - ival, err := strconv.ParseInt(kv[1], 10, 64) - if err == nil { - fields[kv[0]] = ival - } - case "usec_per_call": - fval, err := strconv.ParseFloat(kv[1], 64) - if err == nil { - fields[kv[0]] = fval - } - } + + switch kv[0] { + case "calls": + fallthrough + case "usec": + ival, err := strconv.ParseInt(kv[1], 10, 64) + if err == nil { + fields[kv[0]] = ival + } + case "usec_per_call": + fval, err := strconv.ParseFloat(kv[1], 64) + if err == nil { + fields[kv[0]] = fval } } - acc.AddFields("redis_cmdstat", fields, tags) } + acc.AddFields("redis_cmdstat", fields, tags) } func init() {