summaryrefslogtreecommitdiffstats
path: root/src/go/collectors/go.d.plugin/modules/pika
diff options
context:
space:
mode:
Diffstat (limited to '')
l---------src/go/collectors/go.d.plugin/modules/pika/README.md1
-rw-r--r--src/go/collectors/go.d.plugin/modules/pika/charts.go246
-rw-r--r--src/go/collectors/go.d.plugin/modules/pika/collect.go71
-rw-r--r--src/go/collectors/go.d.plugin/modules/pika/collect_info.go219
-rw-r--r--src/go/collectors/go.d.plugin/modules/pika/config_schema.json93
-rw-r--r--src/go/collectors/go.d.plugin/modules/pika/init.go47
-rw-r--r--src/go/collectors/go.d.plugin/modules/pika/integrations/pika.md221
-rw-r--r--src/go/collectors/go.d.plugin/modules/pika/metadata.yaml277
-rw-r--r--src/go/collectors/go.d.plugin/modules/pika/pika.go134
-rw-r--r--src/go/collectors/go.d.plugin/modules/pika/pika_test.go299
-rw-r--r--src/go/collectors/go.d.plugin/modules/pika/testdata/config.json9
-rw-r--r--src/go/collectors/go.d.plugin/modules/pika/testdata/config.yaml7
-rw-r--r--src/go/collectors/go.d.plugin/modules/pika/testdata/redis/info_all.txt165
-rw-r--r--src/go/collectors/go.d.plugin/modules/pika/testdata/v3.4.0/info_all.txt64
14 files changed, 1853 insertions, 0 deletions
diff --git a/src/go/collectors/go.d.plugin/modules/pika/README.md b/src/go/collectors/go.d.plugin/modules/pika/README.md
new file mode 120000
index 000000000..5e3a8da77
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/pika/README.md
@@ -0,0 +1 @@
+integrations/pika.md \ No newline at end of file
diff --git a/src/go/collectors/go.d.plugin/modules/pika/charts.go b/src/go/collectors/go.d.plugin/modules/pika/charts.go
new file mode 100644
index 000000000..cdaa68f6e
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/pika/charts.go
@@ -0,0 +1,246 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package pika
+
+import "github.com/netdata/netdata/go/go.d.plugin/agent/module"
+
+var pikaCharts = module.Charts{
+ chartConnections.Copy(),
+ chartClients.Copy(),
+
+ chartMemory.Copy(),
+
+ chartConnectedReplicas.Copy(),
+
+ chartCommands.Copy(),
+ chartCommandsCalls.Copy(),
+
+ chartDbStringsKeys.Copy(),
+ chartDbStringsExpiresKeys.Copy(),
+ chartDbStringsInvalidKeys.Copy(),
+ chartDbHashesKeys.Copy(),
+ chartDbHashesExpiresKeys.Copy(),
+ chartDbHashesInvalidKeys.Copy(),
+ chartDbListsKeys.Copy(),
+ chartDbListsExpiresKeys.Copy(),
+ chartDbListsInvalidKeys.Copy(),
+ chartDbZsetsKeys.Copy(),
+ chartDbZsetsExpiresKeys.Copy(),
+ chartDbZsetsInvalidKeys.Copy(),
+ chartDbSetsKeys.Copy(),
+ chartDbSetsExpiresKeys.Copy(),
+ chartDbSetsInvalidKeys.Copy(),
+
+ chartUptime.Copy(),
+}
+
+var (
+ chartConnections = module.Chart{
+ ID: "connections",
+ Title: "Connections",
+ Units: "connections/s",
+ Fam: "connections",
+ Ctx: "pika.connections",
+ Dims: module.Dims{
+ {ID: "total_connections_received", Name: "accepted", Algo: module.Incremental},
+ },
+ }
+ chartClients = module.Chart{
+ ID: "clients",
+ Title: "Clients",
+ Units: "clients",
+ Fam: "connections",
+ Ctx: "pika.clients",
+ Dims: module.Dims{
+ {ID: "connected_clients", Name: "connected"},
+ },
+ }
+)
+
+var (
+ chartMemory = module.Chart{
+ ID: "memory",
+ Title: "Memory usage",
+ Units: "bytes",
+ Fam: "memory",
+ Ctx: "pika.memory",
+ Type: module.Area,
+ Dims: module.Dims{
+ {ID: "used_memory", Name: "used"},
+ },
+ }
+)
+
+var (
+ chartConnectedReplicas = module.Chart{
+ ID: "connected_replicas",
+ Title: "Connected replicas",
+ Units: "replicas",
+ Fam: "replication",
+ Ctx: "pika.connected_replicas",
+ Dims: module.Dims{
+ {ID: "connected_slaves", Name: "connected"},
+ },
+ }
+)
+
+var (
+ chartCommands = module.Chart{
+ ID: "commands",
+ Title: "Processed commands",
+ Units: "commands/s",
+ Fam: "commands",
+ Ctx: "pika.commands",
+ Dims: module.Dims{
+ {ID: "total_commands_processed", Name: "processed", Algo: module.Incremental},
+ },
+ }
+ chartCommandsCalls = module.Chart{
+ ID: "commands_calls",
+ Title: "Calls per command",
+ Units: "calls/s",
+ Fam: "commands",
+ Ctx: "pika.commands_calls",
+ Type: module.Stacked,
+ }
+)
+
+var (
+ chartDbStringsKeys = module.Chart{
+ ID: "database_strings_keys",
+ Title: "Strings type keys per database",
+ Units: "keys",
+ Fam: "keyspace strings",
+ Ctx: "pika.database_strings_keys",
+ Type: module.Stacked,
+ }
+ chartDbStringsExpiresKeys = module.Chart{
+ ID: "database_strings_expires_keys",
+ Title: "Strings type expires keys per database",
+ Units: "keys",
+ Fam: "keyspace strings",
+ Ctx: "pika.database_strings_expires_keys",
+ Type: module.Stacked,
+ }
+ chartDbStringsInvalidKeys = module.Chart{
+ ID: "database_strings_invalid_keys",
+ Title: "Strings type invalid keys per database",
+ Units: "keys",
+ Fam: "keyspace strings",
+ Ctx: "pika.database_strings_invalid_keys",
+ Type: module.Stacked,
+ }
+
+ chartDbHashesKeys = module.Chart{
+ ID: "database_hashes_keys",
+ Title: "Hashes type keys per database",
+ Units: "keys",
+ Fam: "keyspace hashes",
+ Ctx: "pika.database_hashes_keys",
+ Type: module.Stacked,
+ }
+ chartDbHashesExpiresKeys = module.Chart{
+ ID: "database_hashes_expires_keys",
+ Title: "Hashes type expires keys per database",
+ Units: "keys",
+ Fam: "keyspace hashes",
+ Ctx: "pika.database_hashes_expires_keys",
+ Type: module.Stacked,
+ }
+ chartDbHashesInvalidKeys = module.Chart{
+ ID: "database_hashes_invalid_keys",
+ Title: "Hashes type invalid keys per database",
+ Units: "keys",
+ Fam: "keyspace hashes",
+ Ctx: "pika.database_hashes_invalid_keys",
+ Type: module.Stacked,
+ }
+
+ chartDbListsKeys = module.Chart{
+ ID: "database_lists_keys",
+ Title: "Lists type keys per database",
+ Units: "keys",
+ Fam: "keyspace lists",
+ Ctx: "pika.database_lists_keys",
+ Type: module.Stacked,
+ }
+ chartDbListsExpiresKeys = module.Chart{
+ ID: "database_lists_expires_keys",
+ Title: "Lists type expires keys per database",
+ Units: "keys",
+ Fam: "keyspace lists",
+ Ctx: "pika.database_lists_expires_keys",
+ Type: module.Stacked,
+ }
+ chartDbListsInvalidKeys = module.Chart{
+ ID: "database_lists_invalid_keys",
+ Title: "Lists type invalid keys per database",
+ Units: "keys",
+ Fam: "keyspace lists",
+ Ctx: "pika.database_lists_invalid_keys",
+ Type: module.Stacked,
+ }
+
+ chartDbZsetsKeys = module.Chart{
+ ID: "database_zsets_keys",
+ Title: "Zsets type keys per database",
+ Units: "keys",
+ Fam: "keyspace zsets",
+ Ctx: "pika.database_zsets_keys",
+ Type: module.Stacked,
+ }
+ chartDbZsetsExpiresKeys = module.Chart{
+ ID: "database_zsets_expires_keys",
+ Title: "Zsets type expires keys per database",
+ Units: "keys",
+ Fam: "keyspace zsets",
+ Ctx: "pika.database_zsets_expires_keys",
+ Type: module.Stacked,
+ }
+ chartDbZsetsInvalidKeys = module.Chart{
+ ID: "database_zsets_invalid_keys",
+ Title: "Zsets type invalid keys per database",
+ Units: "keys",
+ Fam: "keyspace zsets",
+ Ctx: "pika.database_zsets_invalid_keys",
+ Type: module.Stacked,
+ }
+
+ chartDbSetsKeys = module.Chart{
+ ID: "database_sets_keys",
+ Title: "Sets type keys per database",
+ Units: "keys",
+ Fam: "keyspace sets",
+ Ctx: "pika.database_sets_keys",
+ Type: module.Stacked,
+ }
+ chartDbSetsExpiresKeys = module.Chart{
+ ID: "database_sets_expires_keys",
+ Title: "Sets type expires keys per database",
+ Units: "keys",
+ Fam: "keyspace sets",
+ Ctx: "pika.database_sets_expires_keys",
+ Type: module.Stacked,
+ }
+ chartDbSetsInvalidKeys = module.Chart{
+ ID: "database_sets_invalid_keys",
+ Title: "Sets invalid keys per database",
+ Units: "keys",
+ Fam: "keyspace sets",
+ Ctx: "pika.database_sets_invalid_keys",
+ Type: module.Stacked,
+ }
+)
+
+var (
+ chartUptime = module.Chart{
+ ID: "uptime",
+ Title: "Uptime",
+ Units: "seconds",
+ Fam: "uptime",
+ Ctx: "pika.uptime",
+ Dims: module.Dims{
+ {ID: "uptime_in_seconds", Name: "uptime"},
+ },
+ }
+)
diff --git a/src/go/collectors/go.d.plugin/modules/pika/collect.go b/src/go/collectors/go.d.plugin/modules/pika/collect.go
new file mode 100644
index 000000000..72a4961dd
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/pika/collect.go
@@ -0,0 +1,71 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package pika
+
+import (
+ "bufio"
+ "context"
+ "errors"
+ "fmt"
+ "regexp"
+ "strings"
+
+ "github.com/blang/semver/v4"
+)
+
+const precision = 1000 // float values multiplier and dimensions divisor
+
+func (p *Pika) collect() (map[string]int64, error) {
+ info, err := p.pdb.Info(context.Background(), "all").Result()
+ if err != nil {
+ return nil, err
+ }
+
+ if p.server == "" {
+ s, v, err := extractServerVersion(info)
+ if err != nil {
+ return nil, fmt.Errorf("can not extract server app and version: %v", err)
+ }
+ p.server, p.version = s, v
+ p.Debugf(`server="%s",version="%s"`, s, v)
+ }
+
+ if p.server != "pika" {
+ return nil, fmt.Errorf("unsupported server app, want=pika, got=%s", p.server)
+ }
+
+ ms := make(map[string]int64)
+ p.collectInfo(ms, info)
+
+ return ms, nil
+}
+
+// pika_version:3.4.0
+var reVersion = regexp.MustCompile(`([a-z]+)_version:(\d+\.\d+\.\d+)`)
+
+func extractServerVersion(info string) (string, *semver.Version, error) {
+ var versionLine string
+ for sc := bufio.NewScanner(strings.NewReader(info)); sc.Scan(); {
+ line := sc.Text()
+ if strings.Contains(line, "_version") {
+ versionLine = strings.TrimSpace(line)
+ break
+ }
+ }
+ if versionLine == "" {
+ return "", nil, errors.New("no version property")
+ }
+
+ match := reVersion.FindStringSubmatch(versionLine)
+ if match == nil {
+ return "", nil, fmt.Errorf("can not parse version property '%s'", versionLine)
+ }
+
+ server, version := match[1], match[2]
+ ver, err := semver.New(version)
+ if err != nil {
+ return "", nil, err
+ }
+
+ return server, ver, nil
+}
diff --git a/src/go/collectors/go.d.plugin/modules/pika/collect_info.go b/src/go/collectors/go.d.plugin/modules/pika/collect_info.go
new file mode 100644
index 000000000..2dc68f529
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/pika/collect_info.go
@@ -0,0 +1,219 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package pika
+
+import (
+ "bufio"
+ "regexp"
+ "strconv"
+ "strings"
+
+ "github.com/netdata/netdata/go/go.d.plugin/agent/module"
+)
+
+// https://github.com/Qihoo360/pika/blob/master/src/pika_admin.cc
+// https://github.com/Qihoo360/pika/blob/a0dbdcf5897dd7800ba8a4d1eafce1595619ddc8/src/pika_admin.cc#L694-L710
+
+const (
+ infoSectionServer = "# Server"
+ infoSectionData = "# Data"
+ infoSectionClients = "# Clients"
+ infoSectionStats = "# Stats"
+ infoSectionCommandExecCount = "# Command_Exec_Count"
+ infoSectionCPU = "# CPU"
+ infoSectionReplMaster = "# Replication(MASTER)"
+ infoSectionReplSlave = "# Replication(SLAVE)"
+ infoSectionReplMasterSlave = "# Replication(Master && SLAVE)"
+ infoSectionKeyspace = "# Keyspace"
+)
+
+var infoSections = map[string]struct{}{
+ infoSectionServer: {},
+ infoSectionData: {},
+ infoSectionClients: {},
+ infoSectionStats: {},
+ infoSectionCommandExecCount: {},
+ infoSectionCPU: {},
+ infoSectionReplMaster: {},
+ infoSectionReplSlave: {},
+ infoSectionReplMasterSlave: {},
+ infoSectionKeyspace: {},
+}
+
+func isInfoSection(line string) bool { _, ok := infoSections[line]; return ok }
+
+func (p *Pika) collectInfo(ms map[string]int64, info string) {
+ var curSection string
+
+ sc := bufio.NewScanner(strings.NewReader(info))
+ for sc.Scan() {
+ line := strings.TrimSpace(sc.Text())
+ if len(line) == 0 {
+ curSection = ""
+ continue
+ }
+ if strings.HasPrefix(line, "#") {
+ if isInfoSection(line) {
+ curSection = line
+ }
+ continue
+ }
+
+ field, value, ok := parseProperty(line)
+ if !ok {
+ continue
+ }
+
+ switch curSection {
+ case infoSectionCommandExecCount:
+ p.collectInfoCommandExecCountProperty(ms, field, value)
+ case infoSectionKeyspace:
+ p.collectInfoKeyspaceProperty(ms, field, value)
+ default:
+ collectNumericValue(ms, field, value)
+ }
+ }
+}
+
+var reKeyspaceValue = regexp.MustCompile(`^(.+)_keys=(\d+), expires=(\d+), invalid_keys=(\d+)`)
+
+func (p *Pika) collectInfoKeyspaceProperty(ms map[string]int64, field, value string) {
+ match := reKeyspaceValue.FindStringSubmatch(value)
+ if match == nil {
+ return
+ }
+
+ dataType, keys, expires, invalid := strings.ToLower(match[1]), match[2], match[3], match[4]
+ collectNumericValue(ms, field+"_"+dataType+"_keys", keys)
+ collectNumericValue(ms, field+"_"+dataType+"_expires_keys", expires)
+ collectNumericValue(ms, field+"_"+dataType+"_invalid_keys", invalid)
+
+ if !p.collectedDbs[field] {
+ p.collectedDbs[field] = true
+ p.addDbToKeyspaceCharts(field)
+ }
+}
+
+func (p *Pika) collectInfoCommandExecCountProperty(ms map[string]int64, field, value string) {
+ collectNumericValue(ms, "cmd_"+field+"_calls", value)
+
+ if !p.collectedCommands[field] {
+ p.collectedCommands[field] = true
+ p.addCmdToCommandsCharts(field)
+ }
+}
+
+func (p *Pika) addCmdToCommandsCharts(cmd string) {
+ p.addDimToChart(chartCommandsCalls.ID, &module.Dim{
+ ID: "cmd_" + cmd + "_calls",
+ Name: cmd,
+ Algo: module.Incremental,
+ })
+}
+
+func (p *Pika) addDbToKeyspaceCharts(db string) {
+ p.addDimToChart(chartDbStringsKeys.ID, &module.Dim{
+ ID: db + "_strings_keys",
+ Name: db,
+ })
+ p.addDimToChart(chartDbStringsExpiresKeys.ID, &module.Dim{
+ ID: db + "_strings_expires_keys",
+ Name: db,
+ })
+ p.addDimToChart(chartDbStringsInvalidKeys.ID, &module.Dim{
+ ID: db + "_strings_invalid_keys",
+ Name: db,
+ })
+
+ p.addDimToChart(chartDbHashesKeys.ID, &module.Dim{
+ ID: db + "_hashes_keys",
+ Name: db,
+ })
+ p.addDimToChart(chartDbHashesExpiresKeys.ID, &module.Dim{
+ ID: db + "_hashes_expires_keys",
+ Name: db,
+ })
+ p.addDimToChart(chartDbHashesInvalidKeys.ID, &module.Dim{
+ ID: db + "_hashes_invalid_keys",
+ Name: db,
+ })
+
+ p.addDimToChart(chartDbListsKeys.ID, &module.Dim{
+ ID: db + "_lists_keys",
+ Name: db,
+ })
+ p.addDimToChart(chartDbListsExpiresKeys.ID, &module.Dim{
+ ID: db + "_lists_expires_keys",
+ Name: db,
+ })
+ p.addDimToChart(chartDbListsInvalidKeys.ID, &module.Dim{
+ ID: db + "_lists_invalid_keys",
+ Name: db,
+ })
+
+ p.addDimToChart(chartDbZsetsKeys.ID, &module.Dim{
+ ID: db + "_zsets_keys",
+ Name: db,
+ })
+ p.addDimToChart(chartDbZsetsExpiresKeys.ID, &module.Dim{
+ ID: db + "_zsets_expires_keys",
+ Name: db,
+ })
+ p.addDimToChart(chartDbZsetsInvalidKeys.ID, &module.Dim{
+ ID: db + "_zsets_invalid_keys",
+ Name: db,
+ })
+
+ p.addDimToChart(chartDbSetsKeys.ID, &module.Dim{
+ ID: db + "_sets_keys",
+ Name: db,
+ })
+ p.addDimToChart(chartDbSetsExpiresKeys.ID, &module.Dim{
+ ID: db + "_sets_expires_keys",
+ Name: db,
+ })
+ p.addDimToChart(chartDbSetsInvalidKeys.ID, &module.Dim{
+ ID: db + "_sets_invalid_keys",
+ Name: db,
+ })
+}
+
+func (p *Pika) addDimToChart(chartID string, dim *module.Dim) {
+ chart := p.Charts().Get(chartID)
+ if chart == nil {
+ p.Warningf("error on adding '%s' dimension: can not find '%s' chart", dim.ID, chartID)
+ return
+ }
+ if err := chart.AddDim(dim); err != nil {
+ p.Warning(err)
+ return
+ }
+ chart.MarkNotCreated()
+}
+
+func parseProperty(prop string) (field, value string, ok bool) {
+ var sep byte
+ if strings.HasPrefix(prop, "db") {
+ sep = ' '
+ } else {
+ sep = ':'
+ }
+ i := strings.IndexByte(prop, sep)
+ if i == -1 {
+ return "", "", false
+ }
+ field, value = prop[:i], prop[i+1:]
+ return field, value, field != "" && value != ""
+}
+
+func collectNumericValue(ms map[string]int64, field, value string) {
+ v, err := strconv.ParseFloat(value, 64)
+ if err != nil {
+ return
+ }
+ if strings.IndexByte(value, '.') == -1 {
+ ms[field] = int64(v)
+ } else {
+ ms[field] = int64(v * precision)
+ }
+}
diff --git a/src/go/collectors/go.d.plugin/modules/pika/config_schema.json b/src/go/collectors/go.d.plugin/modules/pika/config_schema.json
new file mode 100644
index 000000000..885cbed0f
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/pika/config_schema.json
@@ -0,0 +1,93 @@
+{
+ "jsonSchema": {
+ "$schema": "http://json-schema.org/draft-07/schema#",
+ "type": "object",
+ "title": "Pika collector configuration.",
+ "properties": {
+ "update_every": {
+ "title": "Update every",
+ "description": "Data collection interval, measured in seconds.",
+ "type": "integer",
+ "minimum": 1,
+ "default": 1
+ },
+ "address": {
+ "title": "URI",
+ "description": "The URI specifying the connection details for the Pika server.",
+ "type": "string",
+ "default": "redis://@localhost:9221"
+ },
+ "timeout": {
+ "title": "Timeout",
+ "description": "Timeout for establishing a connection and communication (reading and writing) in seconds.",
+ "type": "number",
+ "minimum": 0.5,
+ "default": 1
+ },
+ "tls_skip_verify": {
+ "title": "Skip TLS verification",
+ "description": "If set, TLS certificate verification will be skipped.",
+ "type": "boolean"
+ },
+ "tls_ca": {
+ "title": "TLS CA",
+ "description": "The path to the CA certificate file for TLS verification.",
+ "type": "string",
+ "pattern": "^$|^/"
+ },
+ "tls_cert": {
+ "title": "TLS certificate",
+ "description": "The path to the client certificate file for TLS authentication.",
+ "type": "string",
+ "pattern": "^$|^/"
+ },
+ "tls_key": {
+ "title": "TLS key",
+ "description": "The path to the client key file for TLS authentication.",
+ "type": "string",
+ "pattern": "^$|^/"
+ }
+ },
+ "required": [
+ "address"
+ ],
+ "additionalProperties": false,
+ "patternProperties": {
+ "^name$": {}
+ }
+ },
+ "uiSchema": {
+ "uiOptions": {
+ "fullPage": true
+ },
+ "address": {
+ "ui:placeholder": "redis://user:password@host:port",
+ "ui:help": "Tcp connection: `redis://user:password@host:port`. Unix connection: `unix://user:password@/path/to/redis.sock`."
+ },
+ "timeout": {
+ "ui:help": "Accepts decimals for precise control (e.g., type 1.5 for 1.5 seconds)."
+ },
+ "ui:flavour": "tabs",
+ "ui:options": {
+ "tabs": [
+ {
+ "title": "Base",
+ "fields": [
+ "update_every",
+ "address",
+ "timeout"
+ ]
+ },
+ {
+ "title": "TLS",
+ "fields": [
+ "tls_skip_verify",
+ "tls_ca",
+ "tls_cert",
+ "tls_key"
+ ]
+ }
+ ]
+ }
+ }
+}
diff --git a/src/go/collectors/go.d.plugin/modules/pika/init.go b/src/go/collectors/go.d.plugin/modules/pika/init.go
new file mode 100644
index 000000000..8cb62aa52
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/pika/init.go
@@ -0,0 +1,47 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package pika
+
+import (
+ "errors"
+
+ "github.com/netdata/netdata/go/go.d.plugin/agent/module"
+ "github.com/netdata/netdata/go/go.d.plugin/pkg/tlscfg"
+
+ "github.com/go-redis/redis/v8"
+)
+
+func (p *Pika) validateConfig() error {
+ if p.Address == "" {
+ return errors.New("'address' not set")
+ }
+ return nil
+}
+
+func (p *Pika) initRedisClient() (*redis.Client, error) {
+ opts, err := redis.ParseURL(p.Address)
+ if err != nil {
+ return nil, err
+ }
+
+ tlsConfig, err := tlscfg.NewTLSConfig(p.TLSConfig)
+ if err != nil {
+ return nil, err
+ }
+
+ if opts.TLSConfig != nil && tlsConfig != nil {
+ tlsConfig.ServerName = opts.TLSConfig.ServerName
+ }
+
+ opts.PoolSize = 1
+ opts.TLSConfig = tlsConfig
+ opts.DialTimeout = p.Timeout.Duration()
+ opts.ReadTimeout = p.Timeout.Duration()
+ opts.WriteTimeout = p.Timeout.Duration()
+
+ return redis.NewClient(opts), nil
+}
+
+func (p *Pika) initCharts() (*module.Charts, error) {
+ return pikaCharts.Copy(), nil
+}
diff --git a/src/go/collectors/go.d.plugin/modules/pika/integrations/pika.md b/src/go/collectors/go.d.plugin/modules/pika/integrations/pika.md
new file mode 100644
index 000000000..1214dcad7
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/pika/integrations/pika.md
@@ -0,0 +1,221 @@
+<!--startmeta
+custom_edit_url: "https://github.com/netdata/netdata/edit/master/src/go/collectors/go.d.plugin/modules/pika/README.md"
+meta_yaml: "https://github.com/netdata/netdata/edit/master/src/go/collectors/go.d.plugin/modules/pika/metadata.yaml"
+sidebar_label: "Pika"
+learn_status: "Published"
+learn_rel_path: "Collecting Metrics/Databases"
+most_popular: False
+message: "DO NOT EDIT THIS FILE DIRECTLY, IT IS GENERATED BY THE COLLECTOR'S metadata.yaml FILE"
+endmeta-->
+
+# Pika
+
+
+<img src="https://netdata.cloud/img/pika.svg" width="150"/>
+
+
+Plugin: go.d.plugin
+Module: pika
+
+<img src="https://img.shields.io/badge/maintained%20by-Netdata-%2300ab44" />
+
+## Overview
+
+This collector monitors Pika servers.
+
+It collects information and statistics about the server executing the following commands:
+
+- [`INFO ALL`](https://github.com/OpenAtomFoundation/pika/wiki/pika-info%E4%BF%A1%E6%81%AF%E8%AF%B4%E6%98%8E)
+
+
+
+
+This collector is supported on all platforms.
+
+This collector supports collecting metrics from multiple instances of this integration, including remote instances.
+
+
+### Default Behavior
+
+#### Auto-Detection
+
+This integration doesn't support auto-detection.
+
+#### Limits
+
+The default configuration for this integration does not impose any limits on data collection.
+
+#### Performance Impact
+
+The default configuration for this integration is not expected to impose a significant performance impact on the system.
+
+
+## Metrics
+
+Metrics grouped by *scope*.
+
+The scope defines the instance that the metric belongs to. An instance is uniquely identified by a set of labels.
+
+
+
+### Per Pika instance
+
+These metrics refer to the entire monitored application.
+
+This scope has no labels.
+
+Metrics:
+
+| Metric | Dimensions | Unit |
+|:------|:----------|:----|
+| pika.connections | accepted | connections |
+| pika.clients | connected | clients |
+| pika.memory | used | bytes |
+| pika.connected_replicas | connected | replicas |
+| pika.commands | processed | commands/s |
+| pika.commands_calls | a dimension per command | calls/s |
+| pika.database_strings_keys | a dimension per database | keys |
+| pika.database_strings_expires_keys | a dimension per database | keys |
+| pika.database_strings_invalid_keys | a dimension per database | keys |
+| pika.database_hashes_keys | a dimension per database | keys |
+| pika.database_hashes_expires_keys | a dimension per database | keys |
+| pika.database_hashes_invalid_keys | a dimension per database | keys |
+| pika.database_lists_keys | a dimension per database | keys |
+| pika.database_lists_expires_keys | a dimension per database | keys |
+| pika.database_lists_invalid_keys | a dimension per database | keys |
+| pika.database_zsets_keys | a dimension per database | keys |
+| pika.database_zsets_expires_keys | a dimension per database | keys |
+| pika.database_zsets_invalid_keys | a dimension per database | keys |
+| pika.database_sets_keys | a dimension per database | keys |
+| pika.database_sets_expires_keys | a dimension per database | keys |
+| pika.database_sets_invalid_keys | a dimension per database | keys |
+| pika.uptime | uptime | seconds |
+
+
+
+## Alerts
+
+There are no alerts configured by default for this integration.
+
+
+## Setup
+
+### Prerequisites
+
+No action required.
+
+### Configuration
+
+#### File
+
+The configuration file name for this integration is `go.d/pika.conf`.
+
+
+You can edit the configuration file using the `edit-config` script from the
+Netdata [config directory](/docs/netdata-agent/configuration/README.md#the-netdata-config-directory).
+
+```bash
+cd /etc/netdata 2>/dev/null || cd /opt/netdata/etc/netdata
+sudo ./edit-config go.d/pika.conf
+```
+#### Options
+
+The following options can be defined globally: update_every, autodetection_retry.
+
+
+<details open><summary>Config options</summary>
+
+| Name | Description | Default | Required |
+|:----|:-----------|:-------|:--------:|
+| update_every | Data collection frequency. | 5 | no |
+| autodetection_retry | Recheck interval in seconds. Zero means no recheck will be scheduled. | 0 | no |
+| address | Pika server address. | redis://@localhost:9221 | yes |
+| timeout | Dial (establishing new connections), read (socket reads) and write (socket writes) timeout in seconds. | 1 | no |
+| username | Username used for authentication. | | no |
+| password | Password used for authentication. | | no |
+| tls_skip_verify | Server certificate chain and hostname validation policy. Controls whether the client performs this check. | no | no |
+| tls_ca | Certificate authority that client use when verifying server certificates. | | no |
+| tls_cert | Client tls certificate. | | no |
+| tls_key | Client tls key. | | no |
+
+</details>
+
+#### Examples
+
+##### TCP socket
+
+An example configuration.
+
+<details open><summary>Config</summary>
+
+```yaml
+jobs:
+ - name: local
+ address: 'redis://@localhost:9221'
+
+```
+</details>
+
+##### TCP socket with password
+
+An example configuration.
+
+<details open><summary>Config</summary>
+
+```yaml
+jobs:
+ - name: local
+ address: 'redis://:password@127.0.0.1:9221'
+
+```
+</details>
+
+##### Multi-instance
+
+> **Note**: When you define multiple jobs, their names must be unique.
+
+Local and remote instances.
+
+
+<details open><summary>Config</summary>
+
+```yaml
+jobs:
+ - name: local
+ address: 'redis://:password@127.0.0.1:9221'
+
+ - name: remote
+ address: 'redis://user:password@203.0.113.0:9221'
+
+```
+</details>
+
+
+
+## Troubleshooting
+
+### Debug Mode
+
+To troubleshoot issues with the `pika` collector, run the `go.d.plugin` with the debug option enabled. The output
+should give you clues as to why the collector isn't working.
+
+- Navigate to the `plugins.d` directory, usually at `/usr/libexec/netdata/plugins.d/`. If that's not the case on
+ your system, open `netdata.conf` and look for the `plugins` setting under `[directories]`.
+
+ ```bash
+ cd /usr/libexec/netdata/plugins.d/
+ ```
+
+- Switch to the `netdata` user.
+
+ ```bash
+ sudo -u netdata -s
+ ```
+
+- Run the `go.d.plugin` to debug the collector:
+
+ ```bash
+ ./go.d.plugin -d -m pika
+ ```
+
+
diff --git a/src/go/collectors/go.d.plugin/modules/pika/metadata.yaml b/src/go/collectors/go.d.plugin/modules/pika/metadata.yaml
new file mode 100644
index 000000000..c87cd9b27
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/pika/metadata.yaml
@@ -0,0 +1,277 @@
+plugin_name: go.d.plugin
+modules:
+ - meta:
+ id: collector-go.d.plugin-pika
+ plugin_name: go.d.plugin
+ module_name: pika
+ monitored_instance:
+ name: Pika
+ link: https://github.com/OpenAtomFoundation/pika
+ icon_filename: pika.svg
+ categories:
+ - data-collection.database-servers
+ keywords:
+ - pika
+ - databases
+ related_resources:
+ integrations:
+ list: []
+ info_provided_to_referring_integrations:
+ description: ""
+ most_popular: false
+ overview:
+ data_collection:
+ metrics_description: |
+ This collector monitors Pika servers.
+
+ It collects information and statistics about the server executing the following commands:
+
+ - [`INFO ALL`](https://github.com/OpenAtomFoundation/pika/wiki/pika-info%E4%BF%A1%E6%81%AF%E8%AF%B4%E6%98%8E)
+ method_description: ""
+ supported_platforms:
+ include: []
+ exclude: []
+ multi_instance: true
+ additional_permissions:
+ description: ""
+ default_behavior:
+ auto_detection:
+ description: ""
+ limits:
+ description: ""
+ performance_impact:
+ description: ""
+ setup:
+ prerequisites:
+ list: []
+ configuration:
+ file:
+ name: go.d/pika.conf
+ options:
+ description: |
+ The following options can be defined globally: update_every, autodetection_retry.
+ folding:
+ title: Config options
+ enabled: true
+ list:
+ - name: update_every
+ description: Data collection frequency.
+ default_value: 5
+ required: false
+ - name: autodetection_retry
+ description: Recheck interval in seconds. Zero means no recheck will be scheduled.
+ default_value: 0
+ required: false
+ - name: address
+ description: Pika server address.
+ default_value: redis://@localhost:9221
+ required: true
+ details: |
+ There are two connection types: by tcp socket and by unix socket.
+
+ - Tcp connection: `redis://<user>:<password>@<host>:<port>/<db_number>`
+ - Unix connection: `unix://<user>:<password>@</path/to/redis.sock>?db=<db_number>`
+ - name: timeout
+ description: Dial (establishing new connections), read (socket reads) and write (socket writes) timeout in seconds.
+ default_value: 1
+ required: false
+ - name: username
+ description: Username used for authentication.
+ default_value: ""
+ required: false
+ - name: password
+ description: Password used for authentication.
+ default_value: ""
+ required: false
+ - name: tls_skip_verify
+ description: Server certificate chain and hostname validation policy. Controls whether the client performs this check.
+ default_value: false
+ required: false
+ - name: tls_ca
+ description: Certificate authority that client use when verifying server certificates.
+ default_value: ""
+ required: false
+ - name: tls_cert
+ description: Client tls certificate.
+ default_value: ""
+ required: false
+ - name: tls_key
+ description: Client tls key.
+ default_value: ""
+ required: false
+ examples:
+ folding:
+ title: Config
+ enabled: true
+ list:
+ - name: TCP socket
+ description: An example configuration.
+ config: |
+ jobs:
+ - name: local
+ address: 'redis://@localhost:9221'
+ - name: TCP socket with password
+ description: An example configuration.
+ config: |
+ jobs:
+ - name: local
+ address: 'redis://:password@127.0.0.1:9221'
+ - name: Multi-instance
+ description: |
+ > **Note**: When you define multiple jobs, their names must be unique.
+
+ Local and remote instances.
+ config: |
+ jobs:
+ - name: local
+ address: 'redis://:password@127.0.0.1:9221'
+
+ - name: remote
+ address: 'redis://user:password@203.0.113.0:9221'
+ troubleshooting:
+ problems:
+ list: []
+ alerts: []
+ metrics:
+ folding:
+ title: Metrics
+ enabled: false
+ description: ""
+ availability: []
+ scopes:
+ - name: global
+ description: These metrics refer to the entire monitored application.
+ labels: []
+ metrics:
+ - name: pika.connections
+ description: Connections
+ unit: connections
+ chart_type: line
+ dimensions:
+ - name: accepted
+ - name: pika.clients
+ description: Clients
+ unit: clients
+ chart_type: line
+ dimensions:
+ - name: connected
+ - name: pika.memory
+ description: Memory usage
+ unit: bytes
+ chart_type: area
+ dimensions:
+ - name: used
+ - name: pika.connected_replicas
+ description: Connected replicas
+ unit: replicas
+ chart_type: line
+ dimensions:
+ - name: connected
+ - name: pika.commands
+ description: Processed commands
+ unit: commands/s
+ chart_type: line
+ dimensions:
+ - name: processed
+ - name: pika.commands_calls
+ description: Calls per command
+ unit: calls/s
+ chart_type: stacked
+ dimensions:
+ - name: a dimension per command
+ - name: pika.database_strings_keys
+ description: Strings type keys per database
+ unit: keys
+ chart_type: stacked
+ dimensions:
+ - name: a dimension per database
+ - name: pika.database_strings_expires_keys
+ description: Strings type expires keys per database
+ unit: keys
+ chart_type: stacked
+ dimensions:
+ - name: a dimension per database
+ - name: pika.database_strings_invalid_keys
+ description: Strings type invalid keys per database
+ unit: keys
+ chart_type: stacked
+ dimensions:
+ - name: a dimension per database
+ - name: pika.database_hashes_keys
+ description: Hashes type keys per database
+ unit: keys
+ chart_type: stacked
+ dimensions:
+ - name: a dimension per database
+ - name: pika.database_hashes_expires_keys
+ description: Hashes type expires keys per database
+ unit: keys
+ chart_type: stacked
+ dimensions:
+ - name: a dimension per database
+ - name: pika.database_hashes_invalid_keys
+ description: Hashes type invalid keys per database
+ unit: keys
+ chart_type: stacked
+ dimensions:
+ - name: a dimension per database
+ - name: pika.database_lists_keys
+ description: Lists type keys per database
+ unit: keys
+ chart_type: stacked
+ dimensions:
+ - name: a dimension per database
+ - name: pika.database_lists_expires_keys
+ description: Lists type expires keys per database
+ unit: keys
+ chart_type: stacked
+ dimensions:
+ - name: a dimension per database
+ - name: pika.database_lists_invalid_keys
+ description: Lists type invalid keys per database
+ unit: keys
+ chart_type: stacked
+ dimensions:
+ - name: a dimension per database
+ - name: pika.database_zsets_keys
+ description: Zsets type keys per database
+ unit: keys
+ chart_type: stacked
+ dimensions:
+ - name: a dimension per database
+ - name: pika.database_zsets_expires_keys
+ description: Zsets type expires keys per database
+ unit: keys
+ chart_type: stacked
+ dimensions:
+ - name: a dimension per database
+ - name: pika.database_zsets_invalid_keys
+ description: Zsets type invalid keys per database
+ unit: keys
+ chart_type: stacked
+ dimensions:
+ - name: a dimension per database
+ - name: pika.database_sets_keys
+ description: Sets type keys per database
+ unit: keys
+ chart_type: stacked
+ dimensions:
+ - name: a dimension per database
+ - name: pika.database_sets_expires_keys
+ description: Sets type expires keys per database
+ unit: keys
+ chart_type: stacked
+ dimensions:
+ - name: a dimension per database
+ - name: pika.database_sets_invalid_keys
+ description: Sets invalid keys per database
+ unit: keys
+ chart_type: stacked
+ dimensions:
+ - name: a dimension per database
+ - name: pika.uptime
+ description: Uptime
+ unit: seconds
+ chart_type: line
+ dimensions:
+ - name: uptime
diff --git a/src/go/collectors/go.d.plugin/modules/pika/pika.go b/src/go/collectors/go.d.plugin/modules/pika/pika.go
new file mode 100644
index 000000000..c7cbd019a
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/pika/pika.go
@@ -0,0 +1,134 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package pika
+
+import (
+ "context"
+ _ "embed"
+ "errors"
+ "time"
+
+ "github.com/netdata/netdata/go/go.d.plugin/agent/module"
+ "github.com/netdata/netdata/go/go.d.plugin/pkg/tlscfg"
+ "github.com/netdata/netdata/go/go.d.plugin/pkg/web"
+
+ "github.com/blang/semver/v4"
+ "github.com/go-redis/redis/v8"
+)
+
+//go:embed "config_schema.json"
+var configSchema string
+
+func init() {
+ module.Register("pika", module.Creator{
+ JobConfigSchema: configSchema,
+ Create: func() module.Module { return New() },
+ Config: func() any { return &Config{} },
+ })
+}
+
+func New() *Pika {
+ return &Pika{
+ Config: Config{
+ Address: "redis://@localhost:9221",
+ Timeout: web.Duration(time.Second),
+ },
+
+ collectedCommands: make(map[string]bool),
+ collectedDbs: make(map[string]bool),
+ }
+}
+
+type Config struct {
+ UpdateEvery int `yaml:"update_every,omitempty" json:"update_every"`
+ Address string `yaml:"address" json:"address"`
+ Timeout web.Duration `yaml:"timeout,omitempty" json:"timeout"`
+ tlscfg.TLSConfig `yaml:",inline" json:""`
+}
+
+type (
+ Pika struct {
+ module.Base
+ Config `yaml:",inline" json:""`
+
+ charts *module.Charts
+
+ pdb redisClient
+
+ server string
+ version *semver.Version
+ collectedCommands map[string]bool
+ collectedDbs map[string]bool
+ }
+ redisClient interface {
+ Info(ctx context.Context, section ...string) *redis.StringCmd
+ Close() error
+ }
+)
+
+func (p *Pika) Configuration() any {
+ return p.Config
+}
+
+func (p *Pika) Init() error {
+ err := p.validateConfig()
+ if err != nil {
+ p.Errorf("config validation: %v", err)
+ return err
+ }
+
+ pdb, err := p.initRedisClient()
+ if err != nil {
+ p.Errorf("init redis client: %v", err)
+ return err
+ }
+ p.pdb = pdb
+
+ charts, err := p.initCharts()
+ if err != nil {
+ p.Errorf("init charts: %v", err)
+ return err
+ }
+ p.charts = charts
+
+ return nil
+}
+
+func (p *Pika) Check() error {
+ mx, err := p.collect()
+ if err != nil {
+ p.Error(err)
+ return err
+ }
+ if len(mx) == 0 {
+ return errors.New("no metrics collected")
+ }
+ return nil
+}
+
+func (p *Pika) Charts() *module.Charts {
+ return p.charts
+}
+
+func (p *Pika) Collect() map[string]int64 {
+ ms, err := p.collect()
+ if err != nil {
+ p.Error(err)
+ }
+
+ if len(ms) == 0 {
+ return nil
+ }
+ return ms
+}
+
+func (p *Pika) Cleanup() {
+ if p.pdb == nil {
+ return
+ }
+ err := p.pdb.Close()
+ if err != nil {
+ p.Warningf("cleanup: error on closing redis client [%s]: %v", p.Address, err)
+ }
+ p.pdb = nil
+}
diff --git a/src/go/collectors/go.d.plugin/modules/pika/pika_test.go b/src/go/collectors/go.d.plugin/modules/pika/pika_test.go
new file mode 100644
index 000000000..5a4e460d7
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/pika/pika_test.go
@@ -0,0 +1,299 @@
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+package pika
+
+import (
+ "context"
+ "errors"
+ "os"
+ "testing"
+
+ "github.com/netdata/netdata/go/go.d.plugin/agent/module"
+ "github.com/netdata/netdata/go/go.d.plugin/pkg/tlscfg"
+
+ "github.com/go-redis/redis/v8"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+var (
+ dataConfigJSON, _ = os.ReadFile("testdata/config.json")
+ dataConfigYAML, _ = os.ReadFile("testdata/config.yaml")
+
+ dataRedisInfoAll, _ = os.ReadFile("testdata/redis/info_all.txt")
+ dataVer340InfoAll, _ = os.ReadFile("testdata/v3.4.0/info_all.txt")
+)
+
+func Test_testDataIsValid(t *testing.T) {
+ for name, data := range map[string][]byte{
+ "dataConfigJSON": dataConfigJSON,
+ "dataConfigYAML": dataConfigYAML,
+ "dataRedisInfoAll": dataRedisInfoAll,
+ "dataVer340InfoAll": dataVer340InfoAll,
+ } {
+ require.NotNil(t, data, name)
+ }
+}
+
+func TestPika_ConfigurationSerialize(t *testing.T) {
+ module.TestConfigurationSerialize(t, &Pika{}, dataConfigJSON, dataConfigYAML)
+}
+
+func TestPika_Init(t *testing.T) {
+ tests := map[string]struct {
+ config Config
+ wantFail bool
+ }{
+ "success on default config": {
+ config: New().Config,
+ },
+ "fails on unset 'address'": {
+ wantFail: true,
+ config: Config{Address: ""},
+ },
+ "fails on invalid 'address' format": {
+ wantFail: true,
+ config: Config{Address: "127.0.0.1:9221"},
+ },
+ "fails on invalid TLSCA": {
+ wantFail: true,
+ config: Config{
+ Address: "redis://@127.0.0.1:9221",
+ TLSConfig: tlscfg.TLSConfig{TLSCA: "testdata/tls"},
+ },
+ },
+ }
+
+ for name, test := range tests {
+ t.Run(name, func(t *testing.T) {
+ pika := New()
+ pika.Config = test.config
+
+ if test.wantFail {
+ assert.Error(t, pika.Init())
+ } else {
+ assert.NoError(t, pika.Init())
+ }
+ })
+ }
+}
+
+func TestPika_Check(t *testing.T) {
+ tests := map[string]struct {
+ prepare func(t *testing.T) *Pika
+ wantFail bool
+ }{
+ "success on valid response v3.4.0": {
+ prepare: preparePikaV340,
+ },
+ "fails on error on Info": {
+ wantFail: true,
+ prepare: preparePikaErrorOnInfo,
+ },
+ "fails on response from not Pika instance": {
+ wantFail: true,
+ prepare: preparePikaWithRedisMetrics,
+ },
+ }
+
+ for name, test := range tests {
+ t.Run(name, func(t *testing.T) {
+ pika := test.prepare(t)
+
+ if test.wantFail {
+ assert.Error(t, pika.Check())
+ } else {
+ assert.NoError(t, pika.Check())
+ }
+ })
+ }
+}
+
+func TestPika_Charts(t *testing.T) {
+ pika := New()
+ require.NoError(t, pika.Init())
+
+ assert.NotNil(t, pika.Charts())
+}
+
+func TestPika_Cleanup(t *testing.T) {
+ pika := New()
+ assert.NotPanics(t, pika.Cleanup)
+
+ require.NoError(t, pika.Init())
+ m := &mockRedisClient{}
+ pika.pdb = m
+
+ pika.Cleanup()
+
+ assert.True(t, m.calledClose)
+}
+
+func TestPika_Collect(t *testing.T) {
+ tests := map[string]struct {
+ prepare func(t *testing.T) *Pika
+ wantCollected map[string]int64
+ }{
+ "success on valid response v3.4.0": {
+ prepare: preparePikaV340,
+ wantCollected: map[string]int64{
+ "cmd_INFO_calls": 1,
+ "cmd_SET_calls": 2,
+ "arch_bits": 64,
+ "connected_clients": 1,
+ "connected_slaves": 0,
+ "db0_hashes_expires_keys": 0,
+ "db0_hashes_invalid_keys": 0,
+ "db0_hashes_keys": 0,
+ "db0_lists_expires_keys": 0,
+ "db0_lists_invalid_keys": 0,
+ "db0_lists_keys": 0,
+ "db0_sets_expires_keys": 0,
+ "db0_sets_invalid_keys": 0,
+ "db0_sets_keys": 0,
+ "db0_strings_expires_keys": 0,
+ "db0_strings_invalid_keys": 0,
+ "db0_strings_keys": 0,
+ "db0_zsets_expires_keys": 0,
+ "db0_zsets_invalid_keys": 0,
+ "db0_zsets_keys": 0,
+ "instantaneous_ops_per_sec": 0,
+ "log_size": 4272814,
+ "process_id": 1,
+ "server_id": 1,
+ "sync_thread_num": 6,
+ "tcp_port": 9221,
+ "thread_num": 1,
+ "total_commands_processed": 3,
+ "total_connections_received": 3,
+ "uptime_in_days": 1,
+ "uptime_in_seconds": 1884,
+ "used_cpu_sys": 158200,
+ "used_cpu_sys_children": 30,
+ "used_cpu_user": 22050,
+ "used_cpu_user_children": 20,
+ "used_memory": 8198,
+ },
+ },
+ "fails on error on Info": {
+ prepare: preparePikaErrorOnInfo,
+ },
+ "fails on response from not Pika instance": {
+ prepare: preparePikaWithRedisMetrics,
+ },
+ }
+
+ for name, test := range tests {
+ t.Run(name, func(t *testing.T) {
+ pika := test.prepare(t)
+
+ ms := pika.Collect()
+
+ assert.Equal(t, test.wantCollected, ms)
+ if len(test.wantCollected) > 0 {
+ ensureCollectedHasAllChartsDimsVarsIDs(t, pika, ms)
+ ensureCollectedCommandsAddedToCharts(t, pika)
+ ensureCollectedDbsAddedToCharts(t, pika)
+ }
+ })
+ }
+}
+
+func preparePikaV340(t *testing.T) *Pika {
+ pika := New()
+ require.NoError(t, pika.Init())
+ pika.pdb = &mockRedisClient{
+ result: dataVer340InfoAll,
+ }
+ return pika
+}
+
+func preparePikaErrorOnInfo(t *testing.T) *Pika {
+ pika := New()
+ require.NoError(t, pika.Init())
+ pika.pdb = &mockRedisClient{
+ errOnInfo: true,
+ }
+ return pika
+}
+
+func preparePikaWithRedisMetrics(t *testing.T) *Pika {
+ pika := New()
+ require.NoError(t, pika.Init())
+ pika.pdb = &mockRedisClient{
+ result: dataRedisInfoAll,
+ }
+ return pika
+}
+
+func ensureCollectedHasAllChartsDimsVarsIDs(t *testing.T, pika *Pika, ms map[string]int64) {
+ for _, chart := range *pika.Charts() {
+ if chart.Obsolete {
+ continue
+ }
+ for _, dim := range chart.Dims {
+ _, ok := ms[dim.ID]
+ assert.Truef(t, ok, "chart '%s' dim '%s': no dim in collected", dim.ID, chart.ID)
+ }
+ for _, v := range chart.Vars {
+ _, ok := ms[v.ID]
+ assert.Truef(t, ok, "chart '%s' dim '%s': no dim in collected", v.ID, chart.ID)
+ }
+ }
+}
+
+func ensureCollectedCommandsAddedToCharts(t *testing.T, pika *Pika) {
+ for _, id := range []string{
+ chartCommandsCalls.ID,
+ } {
+ chart := pika.Charts().Get(id)
+ require.NotNilf(t, chart, "'%s' chart is not in charts", id)
+ assert.Lenf(t, chart.Dims, len(pika.collectedCommands),
+ "'%s' chart unexpected number of dimensions", id)
+ }
+}
+
+func ensureCollectedDbsAddedToCharts(t *testing.T, pika *Pika) {
+ for _, id := range []string{
+ chartDbStringsKeys.ID,
+ chartDbStringsExpiresKeys.ID,
+ chartDbStringsInvalidKeys.ID,
+ chartDbHashesKeys.ID,
+ chartDbHashesExpiresKeys.ID,
+ chartDbHashesInvalidKeys.ID,
+ chartDbListsKeys.ID,
+ chartDbListsExpiresKeys.ID,
+ chartDbListsInvalidKeys.ID,
+ chartDbZsetsKeys.ID,
+ chartDbZsetsExpiresKeys.ID,
+ chartDbZsetsInvalidKeys.ID,
+ chartDbSetsKeys.ID,
+ chartDbSetsExpiresKeys.ID,
+ chartDbSetsInvalidKeys.ID,
+ } {
+ chart := pika.Charts().Get(id)
+ require.NotNilf(t, chart, "'%s' chart is not in charts", id)
+ assert.Lenf(t, chart.Dims, len(pika.collectedDbs),
+ "'%s' chart unexpected number of dimensions", id)
+ }
+}
+
+type mockRedisClient struct {
+ errOnInfo bool
+ result []byte
+ calledClose bool
+}
+
+func (m *mockRedisClient) Info(_ context.Context, _ ...string) (cmd *redis.StringCmd) {
+ if m.errOnInfo {
+ cmd = redis.NewStringResult("", errors.New("error on Info"))
+ } else {
+ cmd = redis.NewStringResult(string(m.result), nil)
+ }
+ return cmd
+}
+
+func (m *mockRedisClient) Close() error {
+ m.calledClose = true
+ return nil
+}
diff --git a/src/go/collectors/go.d.plugin/modules/pika/testdata/config.json b/src/go/collectors/go.d.plugin/modules/pika/testdata/config.json
new file mode 100644
index 000000000..d8ba812ab
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/pika/testdata/config.json
@@ -0,0 +1,9 @@
+{
+ "update_every": 123,
+ "address": "ok",
+ "timeout": 123.123,
+ "tls_ca": "ok",
+ "tls_cert": "ok",
+ "tls_key": "ok",
+ "tls_skip_verify": true
+}
diff --git a/src/go/collectors/go.d.plugin/modules/pika/testdata/config.yaml b/src/go/collectors/go.d.plugin/modules/pika/testdata/config.yaml
new file mode 100644
index 000000000..6a6f6ae69
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/pika/testdata/config.yaml
@@ -0,0 +1,7 @@
+update_every: 123
+address: "ok"
+timeout: 123.123
+tls_ca: "ok"
+tls_cert: "ok"
+tls_key: "ok"
+tls_skip_verify: yes
diff --git a/src/go/collectors/go.d.plugin/modules/pika/testdata/redis/info_all.txt b/src/go/collectors/go.d.plugin/modules/pika/testdata/redis/info_all.txt
new file mode 100644
index 000000000..8ab381620
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/pika/testdata/redis/info_all.txt
@@ -0,0 +1,165 @@
+$4050
+# Server
+redis_version:6.0.9
+redis_git_sha1:00000000
+redis_git_dirty:0
+redis_build_id:12c354e6793cb936
+redis_mode:standalone
+os:Linux 5.4.39-linuxkit x86_64
+arch_bits:64
+multiplexing_api:epoll
+atomicvar_api:atomic-builtin
+gcc_version:8.3.0
+process_id:1
+run_id:5d97fd948bbf6cb68458685fc747f9f9019c3fc4
+tcp_port:6379
+uptime_in_seconds:252812
+uptime_in_days:2
+hz:10
+configured_hz:10
+lru_clock:13181377
+executable:/data/redis-server
+config_file:
+io_threads_active:0
+
+# Clients
+connected_clients:1
+client_recent_max_input_buffer:8
+client_recent_max_output_buffer:0
+blocked_clients:0
+tracking_clients:0
+clients_in_timeout_table:0
+
+# Memory
+used_memory:867160
+used_memory_human:846.84K
+used_memory_rss:3989504
+used_memory_rss_human:3.80M
+used_memory_peak:923360
+used_memory_peak_human:901.72K
+used_memory_peak_perc:93.91%
+used_memory_overhead:803344
+used_memory_startup:803152
+used_memory_dataset:63816
+used_memory_dataset_perc:99.70%
+allocator_allocated:903408
+allocator_active:1208320
+allocator_resident:3723264
+total_system_memory:2084032512
+total_system_memory_human:1.94G
+used_memory_lua:37888
+used_memory_lua_human:37.00K
+used_memory_scripts:0
+used_memory_scripts_human:0B
+number_of_cached_scripts:0
+maxmemory:0
+maxmemory_human:0B
+maxmemory_policy:noeviction
+allocator_frag_ratio:1.34
+allocator_frag_bytes:304912
+allocator_rss_ratio:3.08
+allocator_rss_bytes:2514944
+rss_overhead_ratio:1.07
+rss_overhead_bytes:266240
+mem_fragmentation_ratio:4.96
+mem_fragmentation_bytes:3185848
+mem_not_counted_for_evict:0
+mem_replication_backlog:0
+mem_clients_slaves:0
+mem_clients_normal:0
+mem_aof_buffer:0
+mem_allocator:jemalloc-5.1.0
+active_defrag_running:0
+lazyfree_pending_objects:0
+
+# Persistence
+loading:0
+rdb_changes_since_last_save:0
+rdb_bgsave_in_progress:0
+rdb_last_save_time:1606951667
+rdb_last_bgsave_status:ok
+rdb_last_bgsave_time_sec:0
+rdb_current_bgsave_time_sec:-1
+rdb_last_cow_size:290816
+aof_enabled:0
+aof_rewrite_in_progress:0
+aof_rewrite_scheduled:0
+aof_last_rewrite_time_sec:-1
+aof_current_rewrite_time_sec:-1
+aof_last_bgrewrite_status:ok
+aof_last_write_status:ok
+aof_last_cow_size:0
+module_fork_in_progress:0
+module_fork_last_cow_size:0
+
+# Stats
+total_connections_received:87
+total_commands_processed:161
+instantaneous_ops_per_sec:0
+total_net_input_bytes:2301
+total_net_output_bytes:507187
+instantaneous_input_kbps:0.00
+instantaneous_output_kbps:0.00
+rejected_connections:0
+sync_full:0
+sync_partial_ok:0
+sync_partial_err:0
+expired_keys:0
+expired_stale_perc:0.00
+expired_time_cap_reached_count:0
+expire_cycle_cpu_milliseconds:28362
+evicted_keys:0
+keyspace_hits:2
+keyspace_misses:0
+pubsub_channels:0
+pubsub_patterns:0
+latest_fork_usec:810
+migrate_cached_sockets:0
+slave_expires_tracked_keys:0
+active_defrag_hits:0
+active_defrag_misses:0
+active_defrag_key_hits:0
+active_defrag_key_misses:0
+tracking_total_keys:0
+tracking_total_items:0
+tracking_total_prefixes:0
+unexpected_error_replies:0
+total_reads_processed:250
+total_writes_processed:163
+io_threaded_reads_processed:0
+io_threaded_writes_processed:0
+
+# Replication
+role:master
+connected_slaves:0
+master_replid:3f0ad529c9c59a17834bde8ae85f09f77609ecb1
+master_replid2:0000000000000000000000000000000000000000
+master_repl_offset:0
+second_repl_offset:-1
+repl_backlog_active:0
+repl_backlog_size:1048576
+repl_backlog_first_byte_offset:0
+repl_backlog_histlen:0
+
+# CPU
+used_cpu_sys:630.829091
+used_cpu_user:188.394908
+used_cpu_sys_children:0.020626
+used_cpu_user_children:0.002731
+
+# Modules
+
+# Commandstats
+cmdstat_set:calls=3,usec=140,usec_per_call=46.67
+cmdstat_command:calls=2,usec=2182,usec_per_call=1091.00
+cmdstat_get:calls=2,usec=29,usec_per_call=14.50
+cmdstat_hmset:calls=2,usec=408,usec_per_call=204.00
+cmdstat_hello:calls=1,usec=15,usec_per_call=15.00
+cmdstat_ping:calls=19,usec=286,usec_per_call=15.05
+cmdstat_info:calls=132,usec=37296,usec_per_call=282.55
+
+# Cluster
+cluster_enabled:0
+
+# Keyspace
+db0:keys=4,expires=0,avg_ttl=0
diff --git a/src/go/collectors/go.d.plugin/modules/pika/testdata/v3.4.0/info_all.txt b/src/go/collectors/go.d.plugin/modules/pika/testdata/v3.4.0/info_all.txt
new file mode 100644
index 000000000..ec58524ce
--- /dev/null
+++ b/src/go/collectors/go.d.plugin/modules/pika/testdata/v3.4.0/info_all.txt
@@ -0,0 +1,64 @@
+$1283
+# Server
+pika_version:3.4.0
+pika_git_sha:bd30511bf82038c2c6531b3d84872c9825fe836a
+pika_build_compile_date: Dec 1 2020
+os:Linux 5.4.39-linuxkit x86_64
+arch_bits:64
+process_id:1
+tcp_port:9221
+thread_num:1
+sync_thread_num:6
+uptime_in_seconds:1884
+uptime_in_days:1
+config_file:/pika/conf/pika.conf
+server_id:1
+
+# Data
+db_size:645807
+db_size_human:0M
+log_size:4272814
+log_size_human:4M
+compression:snappy
+used_memory:8198
+used_memory_human:0M
+db_memtable_usage:8072
+db_tablereader_usage:126
+db_fatal:0
+db_fatal_msg:NULL
+
+# Clients
+connected_clients:1
+
+# Stats
+total_connections_received:3
+instantaneous_ops_per_sec:0
+total_commands_processed:3
+is_bgsaving:No
+is_scaning_keyspace:No
+is_compact:No
+compact_cron:
+compact_interval:
+
+# Command_Exec_Count
+INFO:1
+SET:2
+
+# CPU
+used_cpu_sys:158.20
+used_cpu_user:22.05
+used_cpu_sys_children:0.03
+used_cpu_user_children:0.02
+
+# Replication(MASTER)
+role:master
+connected_slaves:0
+db0 binlog_offset=0 589,safety_purge=none
+
+# Keyspace
+# Time:1970-01-01 08:00:00
+db0 Strings_keys=0, expires=0, invalid_keys=0
+db0 Hashes_keys=0, expires=0, invalid_keys=0
+db0 Lists_keys=0, expires=0, invalid_keys=0
+db0 Zsets_keys=0, expires=0, invalid_keys=0
+db0 Sets_keys=0, expires=0, invalid_keys=0