New 'statistics' SDL CLI command 98/7398/3 e-release v0.9.0
authorPetri Ovaska <petri.ovaska@nokia.com>
Thu, 25 Nov 2021 13:07:17 +0000 (15:07 +0200)
committerPetri Ovaska <petri.ovaska@nokia.com>
Thu, 16 Dec 2021 12:54:07 +0000 (14:54 +0200)
Display SDL resource usage statistics.

 Usage:
  sdlcli statistics [flags]

Update also the release-notes.
Version: 0.9.0

Issue-Id: RIC-113
Change-Id: I653d5325a8ff4a989f3cb7d9ab35c0fe9987392e
Signed-off-by: Petri Ovaska <petri.ovaska@nokia.com>
docs/release-notes.rst
internal/cli/cli_private_fn_test.go
internal/cli/statistics.go [new file with mode: 0644]
internal/cli/statistics_test.go [new file with mode: 0644]
internal/cli/types.go
internal/mocks/db_mocks_private_testing.go
internal/sdlgoredis/dbinfo.go
internal/sdlgoredis/dbstatistics.go [new file with mode: 0644]
internal/sdlgoredis/sdlgoredis.go
internal/sdlgoredis/sdlgoredis_test.go

index 319c497..28c15cc 100644 (file)
@@ -29,6 +29,9 @@ This document provides the release notes of the sdlgo.
 
 Version history
 ---------------
+[0.9.0] - 2021-12-16
+
+* SDL CLI tool
 
 [0.8.0] - 2021-09-22
 
index d37cb9e..2a20a51 100644 (file)
@@ -30,4 +30,5 @@ var (
        NewSetCmdForTest        = newSetCmd
        NewRemoveCmdForTest     = newRemoveCmd
        NewNamespacesCmdForTest = newNamespacesCmd
+       NewStatisticsCmd        = newStatisticsCmd
 )
diff --git a/internal/cli/statistics.go b/internal/cli/statistics.go
new file mode 100644 (file)
index 0000000..05608e2
--- /dev/null
@@ -0,0 +1,164 @@
+/*
+   Copyright (c) 2021 AT&T Intellectual Property.
+   Copyright (c) 2018-2021 Nokia.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+/*
+ * This source code is part of the near-RT RIC (RAN Intelligent Controller)
+ * platform project (RICP).
+ */
+
+package cli
+
+import (
+       "fmt"
+       "gerrit.o-ran-sc.org/r/ric-plt/sdlgo/internal/sdlgoredis"
+       "github.com/spf13/cobra"
+       "os"
+       "reflect"
+       "text/tabwriter"
+)
+
+func init() {
+       rootCmd.AddCommand(newStatisticsCmd(newDatabase))
+}
+
+var (
+       statsLong = `Display SDL resource usage statistics`
+
+       statsExample = `  # Show statistics per node
+  sdlcli statistics`
+)
+
+func newStatisticsCmd(dbCreateCb DbCreateCb) *cobra.Command {
+       return &cobra.Command{
+               Use:     "statistics",
+               Short:   "Display statistics.",
+               Long:    statsLong,
+               Example: statsExample,
+               Args:    cobra.ExactArgs(0),
+               RunE: func(cmd *cobra.Command, args []string) error {
+                       statistics, err := runStats(dbCreateCb)
+                       if err != nil {
+                               fmt.Fprintf(os.Stderr, "%s", buf.String())
+                               return err
+                       }
+                       printStatistics(cmd, statistics)
+                       return nil
+               },
+       }
+}
+
+func runStats(dbCreateCb DbCreateCb) ([]*sdlgoredis.DbStatistics, error) {
+       var statistics []*sdlgoredis.DbStatistics
+       for _, dbInst := range dbCreateCb().Instances {
+               dbStatistics, err := dbInst.Statistics()
+               if err != nil {
+                       return nil, err
+               }
+               statistics = append(statistics, dbStatistics)
+       }
+       return statistics, nil
+}
+
+func writeClientsInfo(w *tabwriter.Writer, clients sdlgoredis.ClientsInfoFields) {
+       fmt.Fprintf(w, "\t\t\tConnectedClients:%d\n", clients.ConnectedClients)
+       fmt.Fprintf(w, "\t\t\tClientRecentMaxInputBuffer:%d\n", clients.ClientRecentMaxInputBuffer)
+       fmt.Fprintf(w, "\t\t\tClientRecentMaxOutputBuffer:%d\n", clients.ClientRecentMaxOutputBuffer)
+}
+
+func writeMemoryInfo(w *tabwriter.Writer, memory sdlgoredis.MeroryInfoFields) {
+       fmt.Fprintf(w, "\t\t\tUsedMemory:%d\n", memory.UsedMemory)
+       fmt.Fprintf(w, "\t\t\tUsedMemoryHuman:%s\n", memory.UsedMemoryHuman)
+       fmt.Fprintf(w, "\t\t\tUsedMemoryRss:%d\n", memory.UsedMemoryRss)
+       fmt.Fprintf(w, "\t\t\tUsedMemoryRssHuman:%s\n", memory.UsedMemoryRssHuman)
+       fmt.Fprintf(w, "\t\t\tUsedMemoryPeak:%d\n", memory.UsedMemoryPeak)
+       fmt.Fprintf(w, "\t\t\tUsedMemoryPeakHuman:%s\n", memory.UsedMemoryPeakHuman)
+       fmt.Fprintf(w, "\t\t\tUsedMemoryPeakPerc:%s\n", memory.UsedMemoryPeakPerc)
+       fmt.Fprintf(w, "\t\t\tMemFragmentationRatio:%.2f\n", memory.MemFragmentationRatio)
+       fmt.Fprintf(w, "\t\t\tMemFragmentationBytes:%d\n", memory.MemFragmentationBytes)
+}
+
+func writeStatsInfo(w *tabwriter.Writer, stats sdlgoredis.StatsInfoFields) {
+       fmt.Fprintf(w, "\t\t\tTotalConnectionsReceived:%d\n", stats.TotalConnectionsReceived)
+       fmt.Fprintf(w, "\t\t\tTotalCommandsProcessed:%d\n", stats.TotalCommandsProcessed)
+       fmt.Fprintf(w, "\t\t\tSyncFull:%d\n", stats.SyncFull)
+       fmt.Fprintf(w, "\t\t\tSyncPartialOk:%d\n", stats.SyncPartialOk)
+       fmt.Fprintf(w, "\t\t\tSyncPartialErr:%d\n", stats.SyncPartialErr)
+       fmt.Fprintf(w, "\t\t\tPubsubChannels:%d\n", stats.PubsubChannels)
+}
+
+func writeCpuInfo(w *tabwriter.Writer, cpu sdlgoredis.CpuInfoFields) {
+       fmt.Fprintf(w, "\t\t\tUsedCpuSys:%v\n", cpu.UsedCpuSys)
+       fmt.Fprintf(w, "\t\t\tUsedCpuUser:%v\n", cpu.UsedCpuUser)
+}
+
+func fillCommandstatsInfo(w *tabwriter.Writer, i interface{}, cmdstat string) {
+       stype := reflect.ValueOf(i).Elem()
+       callsField := stype.FieldByName("Calls").Interface()
+       usecField := stype.FieldByName("Usec").Interface()
+       usecPerCallField := stype.FieldByName("UsecPerCall").Interface()
+
+       if callsField.(uint32) > 0 {
+               fmt.Fprintf(w, "\t\t\t%s:calls=%d usec:%d usecPerCall:%.2f\n",
+                       cmdstat, callsField, usecField, usecPerCallField)
+       }
+}
+
+func writeCommandstatsInfo(w *tabwriter.Writer, commandstats sdlgoredis.CommandstatsInfoFields) {
+       fillCommandstatsInfo(w, &commandstats.CmdstatReplconf, "CmdstatReplconf")
+       fillCommandstatsInfo(w, &commandstats.CmdstatKeys, "CmdstatKeys")
+       fillCommandstatsInfo(w, &commandstats.CmdstatRole, "CmdstatRole")
+       fillCommandstatsInfo(w, &commandstats.CmdstatPsync, "CmdstatPsync")
+       fillCommandstatsInfo(w, &commandstats.CmdstatMset, "CmdstatMset")
+       fillCommandstatsInfo(w, &commandstats.CmdstatPublish, "CmdstatPublish")
+       fillCommandstatsInfo(w, &commandstats.CmdstatInfo, "CmdstatInfo")
+       fillCommandstatsInfo(w, &commandstats.CmdstatPing, "CmdstatPing")
+       fillCommandstatsInfo(w, &commandstats.CmdstatClient, "CmdstatClient")
+       fillCommandstatsInfo(w, &commandstats.CmdstatCommand, "CmdstatCommand")
+       fillCommandstatsInfo(w, &commandstats.CmdstatSubscribe, "CmdstatSubscribe")
+       fillCommandstatsInfo(w, &commandstats.CmdstatMonitor, "CmdstatMonitor")
+       fillCommandstatsInfo(w, &commandstats.CmdstatConfig, "CmdstatConfig")
+       fillCommandstatsInfo(w, &commandstats.CmdstatSlaveof, "CmdstatSlaveof")
+}
+
+func writeKeyspaceInfo(w *tabwriter.Writer, keyspace sdlgoredis.KeyspaceInfoFields) {
+       fmt.Fprintf(w, "\t\t\tDb0:keys=%d\n", keyspace.Db.Keys)
+}
+
+func printStatistics(cmd *cobra.Command, statistics []*sdlgoredis.DbStatistics) {
+       w := tabwriter.NewWriter(cmd.OutOrStdout(), 6, 4, 2, ' ', 0)
+       fmt.Fprintln(w, "CLUSTER\tROLE\tADDRESS\tSTATISTICS")
+
+       for i, s := range statistics {
+               for _, stats := range s.Stats {
+                       fmt.Fprintf(w, "%d", i)
+                       if stats.Info.Fields.PrimaryRole {
+                               fmt.Fprintf(w, "\tPrimary")
+                       } else {
+                               fmt.Fprintf(w, "\tReplica")
+                       }
+                       fmt.Fprintf(w, "\t%s:%s", stats.IPAddr, stats.Port)
+                       fmt.Fprintf(w, "\tUptimeInDays:%d\n", stats.Info.Fields.Server.UptimeInDays)
+                       writeClientsInfo(w, stats.Info.Fields.Clients)
+                       writeMemoryInfo(w, stats.Info.Fields.Memory)
+                       writeStatsInfo(w, stats.Info.Fields.Stats)
+                       writeCpuInfo(w, stats.Info.Fields.Cpu)
+                       writeCommandstatsInfo(w, stats.Info.Fields.Commandstats)
+                       writeKeyspaceInfo(w, stats.Info.Fields.Keyspace)
+               }
+       }
+       w.Flush()
+}
diff --git a/internal/cli/statistics_test.go b/internal/cli/statistics_test.go
new file mode 100644 (file)
index 0000000..7ce7736
--- /dev/null
@@ -0,0 +1,448 @@
+/*
+   Copyright (c) 2021 AT&T Intellectual Property.
+   Copyright (c) 2018-2021 Nokia.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+/*
+ * This source code is part of the near-RT RIC (RAN Intelligent Controller)
+ * platform project (RICP).
+ */
+
+package cli_test
+
+import (
+       "bytes"
+       "errors"
+       "gerrit.o-ran-sc.org/r/ric-plt/sdlgo/internal/cli"
+       "gerrit.o-ran-sc.org/r/ric-plt/sdlgo/internal/mocks"
+       "gerrit.o-ran-sc.org/r/ric-plt/sdlgo/internal/sdlgoredis"
+       "github.com/stretchr/testify/assert"
+       "testing"
+)
+
+var columnNamesOut = `CLUSTER  ROLE     ADDRESS            STATISTICS
+`
+var primaryOut = `0        Primary  192.168.10.1:6379  UptimeInDays:1
+                                     ConnectedClients:1
+                                     ClientRecentMaxInputBuffer:2
+                                     ClientRecentMaxOutputBuffer:0
+                                     UsedMemory:2073528
+                                     UsedMemoryHuman:1.98M
+                                     UsedMemoryRss:13721600
+                                     UsedMemoryRssHuman:13.09M
+                                     UsedMemoryPeak:6706008
+                                     UsedMemoryPeakHuman:6.40M
+                                     UsedMemoryPeakPerc:30.92%
+                                     MemFragmentationRatio:6.66
+                                     MemFragmentationBytes:11379232
+                                     TotalConnectionsReceived:566
+                                     TotalCommandsProcessed:3635063
+                                     SyncFull:2
+                                     SyncPartialOk:21
+                                     SyncPartialErr:0
+                                     PubsubChannels:1
+                                     UsedCpuSys:1457.472805
+                                     UsedCpuUser:861.060018
+                                     CmdstatReplconf:calls=1090183 usec:2790911 usecPerCall:2.56
+                                     CmdstatKeys:calls=20 usec:233 usecPerCall:11.65
+                                     CmdstatRole:calls=2 usec:84 usecPerCall:42.00
+                                     CmdstatPsync:calls=23 usec:3582 usecPerCall:155.74
+                                     CmdstatMset:calls=4 usec:30 usecPerCall:7.50
+                                     CmdstatPublish:calls=804867 usec:12965499 usecPerCall:16.11
+                                     CmdstatInfo:calls=164255 usec:57935065 usecPerCall:352.71
+                                     CmdstatPing:calls=1582493 usec:3466844 usecPerCall:2.19
+                                     CmdstatClient:calls=6 usec:11 usecPerCall:2.17
+                                     CmdstatCommand:calls=482 usec:1462633 usecPerCall:3034.51
+                                     CmdstatSubscribe:calls=3 usec:11 usecPerCall:3.67
+                                     CmdstatMonitor:calls=3 usec:6 usecPerCall:2.00
+                                     Db0:keys=123
+`
+var replicaOut = `0        Replica  192.168.10.2:6379  UptimeInDays:2
+                                     ConnectedClients:8
+                                     ClientRecentMaxInputBuffer:3
+                                     ClientRecentMaxOutputBuffer:1
+                                     UsedMemory:2135768
+                                     UsedMemoryHuman:2.04M
+                                     UsedMemoryRss:13721720
+                                     UsedMemoryRssHuman:12.48M
+                                     UsedMemoryPeak:6706009
+                                     UsedMemoryPeakHuman:6.41M
+                                     UsedMemoryPeakPerc:30.93%
+                                     MemFragmentationRatio:6.67
+                                     MemFragmentationBytes:11379233
+                                     TotalConnectionsReceived:567
+                                     TotalCommandsProcessed:3635064
+                                     SyncFull:0
+                                     SyncPartialOk:0
+                                     SyncPartialErr:0
+                                     PubsubChannels:1
+                                     UsedCpuSys:1457.472806
+                                     UsedCpuUser:861.060019
+                                     CmdstatRole:calls=1 usec:3 usecPerCall:32.00
+                                     CmdstatMset:calls=3 usec:14 usecPerCall:7.51
+                                     CmdstatPublish:calls=804868 usec:12965498 usecPerCall:16.15
+                                     CmdstatInfo:calls=164256 usec:57935066 usecPerCall:352.72
+                                     CmdstatPing:calls=1582494 usec:3466845 usecPerCall:2.20
+                                     CmdstatCommand:calls=483 usec:1462634 usecPerCall:3034.52
+                                     CmdstatSubscribe:calls=3 usec:12 usecPerCall:3.68
+                                     CmdstatMonitor:calls=6 usec:11 usecPerCall:2.11
+                                     CmdstatConfig:calls=2 usec:1462 usecPerCall:146.74
+                                     CmdstatSlaveof:calls=1 usec:412 usecPerCall:412.00
+                                     Db0:keys=123
+`
+
+var statsMock *statisticsMock
+
+type statisticsMock struct {
+       dbIface      *mocks.MockDB
+       dbErr        error
+       dbStatistics sdlgoredis.DbStatistics
+}
+
+func newStatisticsDatabaseMock() *cli.Database {
+       db := &cli.Database{}
+       statsMock.dbIface = new(mocks.MockDB)
+       statsMock.dbIface.On("Statistics").Return(&statsMock.dbStatistics, statsMock.dbErr)
+       db.Instances = append(db.Instances, statsMock.dbIface)
+       return db
+}
+
+func setupStatisticsMockDb() {
+       if statsMock == nil {
+               statsMock = new(statisticsMock)
+       }
+       statsMock.dbStatistics.Stats = []*sdlgoredis.DbStatisticsInfo{}
+}
+
+func setupStatisticsTestcase(tb testing.TB) func(tb testing.TB) {
+       setupStatisticsMockDb()
+
+       return func(tb testing.TB) {
+               statsMock.dbStatistics.Stats = nil
+       }
+}
+
+func setupStatisticsMockDbWithPrimaryInfo() {
+       statsMock.dbStatistics.Stats = append(statsMock.dbStatistics.Stats,
+               &sdlgoredis.DbStatisticsInfo{
+                       IPAddr: "192.168.10.1",
+                       Port:   "6379",
+                       Info: &sdlgoredis.DbInfo{
+                               Fields: sdlgoredis.DbInfoFields{
+                                       PrimaryRole: true,
+                                       Server: sdlgoredis.ServerInfoFields{
+                                               UptimeInDays: 1,
+                                       },
+                                       Clients: sdlgoredis.ClientsInfoFields{
+                                               ConnectedClients:            1,
+                                               ClientRecentMaxInputBuffer:  2,
+                                               ClientRecentMaxOutputBuffer: 0,
+                                       },
+                                       Memory: sdlgoredis.MeroryInfoFields{
+                                               UsedMemory:            2073528,
+                                               UsedMemoryHuman:       "1.98M",
+                                               UsedMemoryRss:         13721600,
+                                               UsedMemoryRssHuman:    "13.09M",
+                                               UsedMemoryPeak:        6706008,
+                                               UsedMemoryPeakHuman:   "6.40M",
+                                               UsedMemoryPeakPerc:    "30.92%",
+                                               MemFragmentationRatio: 6.66,
+                                               MemFragmentationBytes: 11379232,
+                                       },
+                                       Stats: sdlgoredis.StatsInfoFields{
+                                               TotalConnectionsReceived: 566,
+                                               TotalCommandsProcessed:   3635063,
+                                               SyncFull:                 2,
+                                               SyncPartialOk:            21,
+                                               SyncPartialErr:           0,
+                                               PubsubChannels:           1,
+                                       },
+                                       Cpu: sdlgoredis.CpuInfoFields{
+                                               UsedCpuSys:  1457.472805,
+                                               UsedCpuUser: 861.060018,
+                                       },
+                                       Commandstats: sdlgoredis.CommandstatsInfoFields{
+                                               CmdstatReplconf: sdlgoredis.CommandstatsValues{
+                                                       Calls:       1090183,
+                                                       Usec:        2790911,
+                                                       UsecPerCall: 2.56,
+                                               },
+                                               CmdstatKeys: sdlgoredis.CommandstatsValues{
+                                                       Calls:       20,
+                                                       Usec:        233,
+                                                       UsecPerCall: 11.65,
+                                               },
+                                               CmdstatRole: sdlgoredis.CommandstatsValues{
+                                                       Calls:       2,
+                                                       Usec:        84,
+                                                       UsecPerCall: 42.00,
+                                               },
+                                               CmdstatConfig: sdlgoredis.CommandstatsValues{},
+                                               CmdstatPsync: sdlgoredis.CommandstatsValues{
+                                                       Calls:       23,
+                                                       Usec:        3582,
+                                                       UsecPerCall: 155.74,
+                                               },
+                                               CmdstatMset: sdlgoredis.CommandstatsValues{
+                                                       Calls:       4,
+                                                       Usec:        30,
+                                                       UsecPerCall: 7.50,
+                                               },
+                                               CmdstatPublish: sdlgoredis.CommandstatsValues{
+                                                       Calls:       804867,
+                                                       Usec:        12965499,
+                                                       UsecPerCall: 16.11,
+                                               },
+                                               CmdstatInfo: sdlgoredis.CommandstatsValues{
+                                                       Calls:       164255,
+                                                       Usec:        57935065,
+                                                       UsecPerCall: 352.71,
+                                               },
+                                               CmdstatPing: sdlgoredis.CommandstatsValues{
+                                                       Calls:       1582493,
+                                                       Usec:        3466844,
+                                                       UsecPerCall: 2.19,
+                                               },
+                                               CmdstatClient: sdlgoredis.CommandstatsValues{
+                                                       Calls:       6,
+                                                       Usec:        11,
+                                                       UsecPerCall: 2.17,
+                                               },
+                                               CmdstatCommand: sdlgoredis.CommandstatsValues{
+                                                       Calls:       482,
+                                                       Usec:        1462633,
+                                                       UsecPerCall: 3034.51,
+                                               },
+                                               CmdstatSubscribe: sdlgoredis.CommandstatsValues{
+                                                       Calls:       3,
+                                                       Usec:        11,
+                                                       UsecPerCall: 3.67,
+                                               },
+                                               CmdstatMonitor: sdlgoredis.CommandstatsValues{
+                                                       Calls:       3,
+                                                       Usec:        6,
+                                                       UsecPerCall: 2.00,
+                                               },
+                                               CmdstatSlaveof: sdlgoredis.CommandstatsValues{},
+                                       },
+                                       Keyspace: sdlgoredis.KeyspaceInfoFields{
+                                               Db: sdlgoredis.KeyspaceValues{
+                                                       Keys: 123,
+                                               },
+                                       },
+                               },
+                       },
+               },
+       )
+}
+
+func setupStatisticsMockDbWithReplicaInfo() {
+       statsMock.dbStatistics.Stats = append(statsMock.dbStatistics.Stats,
+               &sdlgoredis.DbStatisticsInfo{
+                       IPAddr: "192.168.10.2",
+                       Port:   "6379",
+                       Info: &sdlgoredis.DbInfo{
+                               Fields: sdlgoredis.DbInfoFields{
+                                       PrimaryRole: false,
+                                       Server: sdlgoredis.ServerInfoFields{
+                                               UptimeInDays: 2,
+                                       },
+                                       Clients: sdlgoredis.ClientsInfoFields{
+                                               ConnectedClients:            8,
+                                               ClientRecentMaxInputBuffer:  3,
+                                               ClientRecentMaxOutputBuffer: 1,
+                                       },
+                                       Memory: sdlgoredis.MeroryInfoFields{
+                                               UsedMemory:            2135768,
+                                               UsedMemoryHuman:       "2.04M",
+                                               UsedMemoryRss:         13721720,
+                                               UsedMemoryRssHuman:    "12.48M",
+                                               UsedMemoryPeak:        6706009,
+                                               UsedMemoryPeakHuman:   "6.41M",
+                                               UsedMemoryPeakPerc:    "30.93%",
+                                               MemFragmentationRatio: 6.67,
+                                               MemFragmentationBytes: 11379233,
+                                       },
+                                       Stats: sdlgoredis.StatsInfoFields{
+                                               TotalConnectionsReceived: 567,
+                                               TotalCommandsProcessed:   3635064,
+                                               SyncFull:                 0,
+                                               SyncPartialOk:            0,
+                                               SyncPartialErr:           0,
+                                               PubsubChannels:           1,
+                                       },
+                                       Cpu: sdlgoredis.CpuInfoFields{
+                                               UsedCpuSys:  1457.472806,
+                                               UsedCpuUser: 861.060019,
+                                       },
+                                       Commandstats: sdlgoredis.CommandstatsInfoFields{
+                                               CmdstatReplconf: sdlgoredis.CommandstatsValues{},
+                                               CmdstatKeys:     sdlgoredis.CommandstatsValues{},
+                                               CmdstatRole: sdlgoredis.CommandstatsValues{
+                                                       Calls:       1,
+                                                       Usec:        3,
+                                                       UsecPerCall: 32.00,
+                                               },
+                                               CmdstatConfig: sdlgoredis.CommandstatsValues{
+                                                       Calls:       2,
+                                                       Usec:        1462,
+                                                       UsecPerCall: 146.74,
+                                               },
+                                               CmdstatPsync: sdlgoredis.CommandstatsValues{},
+                                               CmdstatMset: sdlgoredis.CommandstatsValues{
+                                                       Calls:       3,
+                                                       Usec:        14,
+                                                       UsecPerCall: 7.51,
+                                               },
+                                               CmdstatPublish: sdlgoredis.CommandstatsValues{
+                                                       Calls:       804868,
+                                                       Usec:        12965498,
+                                                       UsecPerCall: 16.15,
+                                               },
+                                               CmdstatInfo: sdlgoredis.CommandstatsValues{
+                                                       Calls:       164256,
+                                                       Usec:        57935066,
+                                                       UsecPerCall: 352.72,
+                                               },
+                                               CmdstatPing: sdlgoredis.CommandstatsValues{
+                                                       Calls:       1582494,
+                                                       Usec:        3466845,
+                                                       UsecPerCall: 2.20,
+                                               },
+                                               CmdstatClient: sdlgoredis.CommandstatsValues{},
+                                               CmdstatCommand: sdlgoredis.CommandstatsValues{
+                                                       Calls:       483,
+                                                       Usec:        1462634,
+                                                       UsecPerCall: 3034.52,
+                                               },
+                                               CmdstatSubscribe: sdlgoredis.CommandstatsValues{
+                                                       Calls:       3,
+                                                       Usec:        12,
+                                                       UsecPerCall: 3.68,
+                                               },
+                                               CmdstatMonitor: sdlgoredis.CommandstatsValues{
+                                                       Calls:       6,
+                                                       Usec:        11,
+                                                       UsecPerCall: 2.11,
+                                               },
+                                               CmdstatSlaveof: sdlgoredis.CommandstatsValues{
+                                                       Calls:       1,
+                                                       Usec:        412,
+                                                       UsecPerCall: 412.00,
+                                               },
+                                       },
+                                       Keyspace: sdlgoredis.KeyspaceInfoFields{
+                                               Db: sdlgoredis.KeyspaceValues{
+                                                       Keys: 123,
+                                               },
+                                       },
+                               },
+                       },
+               },
+       )
+}
+
+func runStatisticsCli() (string, error) {
+       buf := new(bytes.Buffer)
+       cmd := cli.NewStatisticsCmd(newStatisticsDatabaseMock)
+       cmd.SetOut(buf)
+
+       err := cmd.Execute()
+
+       return buf.String(), err
+}
+
+func TestCliStatisticsCanShowHelp(t *testing.T) {
+       var expOkErr error
+       expHelp := "Usage:\n  " + "statistics [flags]"
+       expNokErr := errors.New("unknown flag: --some-unknown-flag")
+       expArgCntErr := errors.New("accepts 0 arg(s), received 1")
+       tests := []struct {
+               args      string
+               expErr    error
+               expOutput string
+       }{
+               {args: "-h", expErr: expOkErr, expOutput: expHelp},
+               {args: "--help", expErr: expOkErr, expOutput: expHelp},
+               {args: "--some-unknown-flag", expErr: expNokErr, expOutput: expHelp},
+               {args: "some-extra-argument", expErr: expArgCntErr, expOutput: expHelp},
+       }
+
+       for _, test := range tests {
+               buf := new(bytes.Buffer)
+               cmd := cli.NewStatisticsCmd(newStatisticsDatabaseMock)
+               cmd.SetOut(buf)
+               cmd.SetArgs([]string{test.args})
+
+               err := cmd.Execute()
+
+               stdout := buf.String()
+               assert.Equal(t, test.expErr, err)
+               assert.Contains(t, stdout, test.expOutput)
+       }
+}
+
+func TestCliStatisticsShowOnlyColumnsNames(t *testing.T) {
+       teardownTest := setupStatisticsTestcase(t)
+       defer teardownTest(t)
+
+       expOut := "CLUSTER  ROLE  ADDRESS  STATISTICS\n"
+
+       stdout, err := runStatisticsCli()
+
+       assert.Nil(t, err)
+       assert.Equal(t, expOut, stdout)
+}
+
+func TestCliStatisticsShowPrimaryInfo(t *testing.T) {
+       teardownTest := setupStatisticsTestcase(t)
+       defer teardownTest(t)
+
+       setupStatisticsMockDbWithPrimaryInfo()
+       expOut := columnNamesOut + primaryOut
+
+       stdout, err := runStatisticsCli()
+
+       assert.Nil(t, err)
+       assert.Equal(t, expOut, stdout)
+}
+
+func TestCliStatisticsShowPrimaryAndReplicaInfo(t *testing.T) {
+       teardownTest := setupStatisticsTestcase(t)
+       defer teardownTest(t)
+
+       setupStatisticsMockDbWithPrimaryInfo()
+       setupStatisticsMockDbWithReplicaInfo()
+       expOut := columnNamesOut + primaryOut + replicaOut
+
+       stdout, err := runStatisticsCli()
+
+       assert.Nil(t, err)
+       assert.Equal(t, expOut, stdout)
+}
+
+func TestCliStatisticsRaisesError(t *testing.T) {
+       teardownTest := setupStatisticsTestcase(t)
+       defer teardownTest(t)
+
+       expErr := errors.New("Boom!")
+       expOut := "Usage:\n  statistics [flags]"
+       statsMock.dbErr = errors.New("Boom!")
+
+       stdout, err := runStatisticsCli()
+
+       assert.Equal(t, expErr, err)
+       assert.Contains(t, stdout, expOut)
+}
index eb86905..6cfe725 100644 (file)
@@ -30,6 +30,7 @@ type iDatabase interface {
        Info() (*sdlgoredis.DbInfo, error)
        State() (*sdlgoredis.DbState, error)
        Keys(pattern string) ([]string, error)
+       Statistics() (*sdlgoredis.DbStatistics, error)
 }
 
 //Database struct is a holder for the internal database instances.
index 42bb061..1a4e29b 100644 (file)
@@ -46,6 +46,11 @@ func (m *MockDB) Keys(pattern string) ([]string, error) {
        return a.Get(0).([]string), a.Error(1)
 }
 
+func (m *MockDB) Statistics() (*sdlgoredis.DbStatistics, error) {
+       a := m.Called()
+       return a.Get(0).(*sdlgoredis.DbStatistics), a.Error(1)
+}
+
 type MockSdlApi struct {
        mock.Mock
 }
index 931cfb7..6f8a638 100644 (file)
@@ -33,4 +33,80 @@ type DbInfo struct {
 type DbInfoFields struct {
        PrimaryRole         bool
        ConnectedReplicaCnt uint32
+       Server              ServerInfoFields
+       Clients             ClientsInfoFields
+       Memory              MeroryInfoFields
+       Stats               StatsInfoFields
+       Cpu                 CpuInfoFields
+       Commandstats        CommandstatsInfoFields
+       Keyspace            KeyspaceInfoFields
+}
+
+type ServerInfoFields struct {
+       UptimeInDays uint32
+}
+
+type ClientsInfoFields struct {
+       ConnectedClients            uint32
+       ClientRecentMaxInputBuffer  uint32
+       ClientRecentMaxOutputBuffer uint32
+}
+
+type MeroryInfoFields struct {
+       UsedMemory            uint64
+       UsedMemoryHuman       string
+       UsedMemoryRss         uint64
+       UsedMemoryRssHuman    string
+       UsedMemoryPeak        uint64
+       UsedMemoryPeakHuman   string
+       UsedMemoryPeakPerc    string
+       MemFragmentationRatio float32
+       MemFragmentationBytes uint32
+}
+
+type StatsInfoFields struct {
+       TotalConnectionsReceived uint32
+       TotalCommandsProcessed   uint32
+       SyncFull                 uint32
+       SyncPartialOk            uint32
+       SyncPartialErr           uint32
+       PubsubChannels           uint32
+}
+
+type CpuInfoFields struct {
+       UsedCpuSys  float64
+       UsedCpuUser float64
+}
+
+type CommandstatsValues struct {
+       Calls       uint32
+       Usec        uint32
+       UsecPerCall float32
+}
+
+type CommandstatsInfoFields struct {
+       CmdstatReplconf  CommandstatsValues
+       CmdstatKeys      CommandstatsValues
+       CmdstatRole      CommandstatsValues
+       CmdstatConfig    CommandstatsValues
+       CmdstatPsync     CommandstatsValues
+       CmdstatMset      CommandstatsValues
+       CmdstatPublish   CommandstatsValues
+       CmdstatInfo      CommandstatsValues
+       CmdstatPing      CommandstatsValues
+       CmdstatClient    CommandstatsValues
+       CmdstatCommand   CommandstatsValues
+       CmdstatSubscribe CommandstatsValues
+       CmdstatMonitor   CommandstatsValues
+       CmdstatSlaveof   CommandstatsValues
+}
+
+type KeyspaceValues struct {
+       Keys    uint32
+       Expires uint32
+       AvgTtl  uint32
+}
+
+type KeyspaceInfoFields struct {
+       Db KeyspaceValues
 }
diff --git a/internal/sdlgoredis/dbstatistics.go b/internal/sdlgoredis/dbstatistics.go
new file mode 100644 (file)
index 0000000..c4b3d8a
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+   Copyright (c) 2021 AT&T Intellectual Property.
+   Copyright (c) 2018-2021 Nokia.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+/*
+ * This source code is part of the near-RT RIC (RAN Intelligent Controller)
+ * platform project (RICP).
+ */
+
+package sdlgoredis
+
+//DbStatistics struct contains list of DB's Statistics information.
+type DbStatistics struct {
+       Stats []*DbStatisticsInfo
+}
+
+//DbStatisticsInfo struct contains fields for DB Statistics information.
+type DbStatisticsInfo struct {
+       IPAddr string
+       Port   string
+       Info   *DbInfo
+}
index 77dce09..0b93239 100644 (file)
@@ -28,7 +28,9 @@ import (
        "github.com/go-redis/redis/v7"
        "io"
        "log"
+       "net"
        "os"
+       "reflect"
        "strconv"
        "strings"
        "sync"
@@ -499,20 +501,181 @@ func (db *DB) Info() (*DbInfo, error) {
        return &info, err
 }
 
+func lineContains(line, substr string) bool {
+       return strings.Contains(line, substr)
+}
+
+func getFieldValueStr(line, substr string) string {
+       if idx := strings.Index(line, substr); idx != -1 {
+               return line[idx+len(substr):]
+       }
+       return ""
+}
+
+func getUint32FromString(s string) uint32 {
+       if val, err := strconv.ParseUint(s, 10, 32); err == nil {
+               return uint32(val)
+       }
+       return 0
+}
+
+func getUint64FromString(s string) uint64 {
+       if val, err := strconv.ParseUint(s, 10, 64); err == nil {
+               return uint64(val)
+       }
+       return 0
+}
+
+func getFloatFromString(s string, bitSize int) float64 {
+       if val, err := strconv.ParseFloat(s, bitSize); err == nil {
+               return val
+       }
+       return 0
+}
+
+func getFloat64FromString(s string) float64 {
+       return getFloatFromString(s, 64)
+}
+
+func getFloat32FromString(s string) float32 {
+       return float32(getFloatFromString(s, 32))
+}
+
+func getValueString(values string, key string) string {
+       slice := strings.Split(values, ",")
+       for _, s := range slice {
+               if lineContains(s, key) {
+                       return getFieldValueStr(s, key)
+               }
+       }
+       return ""
+}
+
+func getCommandstatsValues(values string) (string, string, string) {
+       calls := getValueString(values, "calls=")
+       usec := getValueString(values, "usec=")
+       usecPerCall := getValueString(values, "usec_per_call=")
+       return calls, usec, usecPerCall
+}
+
+func updateCommandstatsValues(i interface{}, line, cmdstat string) {
+       stype := reflect.ValueOf(i).Elem()
+       values := getFieldValueStr(line, cmdstat)
+       callsStr, usecStr, usecPerCallStr := getCommandstatsValues(values)
+
+       callsField := stype.FieldByName("Calls")
+       callsField.Set(reflect.ValueOf(getUint32FromString(callsStr)))
+
+       usecField := stype.FieldByName("Usec")
+       usecField.Set(reflect.ValueOf(getUint32FromString(usecStr)))
+
+       usecPerCallField := stype.FieldByName("UsecPerCall")
+       usecPerCallField.Set(reflect.ValueOf(getFloat32FromString(usecPerCallStr)))
+}
+
+func getKeyspaceValues(values string) (string, string, string) {
+       keys := getValueString(values, "keys=")
+       expires := getValueString(values, "expires=")
+       avgttl := getValueString(values, "avg_ttl=")
+       return keys, expires, avgttl
+}
+
+func updateKeyspaceValues(i interface{}, line, keyspace string) {
+       stype := reflect.ValueOf(i).Elem()
+       values := getFieldValueStr(line, keyspace)
+       keysStr, expiresStr, avgttlStr := getKeyspaceValues(values)
+
+       keysField := stype.FieldByName("Keys")
+       keysField.Set(reflect.ValueOf(getUint32FromString(keysStr)))
+
+       expiresField := stype.FieldByName("Expires")
+       expiresField.Set(reflect.ValueOf(getUint32FromString(expiresStr)))
+
+       avgttlField := stype.FieldByName("AvgTtl")
+       avgttlField.Set(reflect.ValueOf(getUint32FromString(avgttlStr)))
+}
+
 func readRedisInfoReplyFields(input []string, info *DbInfo) error {
        for _, line := range input {
-               if idx := strings.Index(line, "role:"); idx != -1 {
-                       roleStr := line[idx+len("role:"):]
-                       if roleStr == "master" {
+               switch {
+               case lineContains(line, "role:") && !lineContains(line, "_role:"):
+                       if "master" == getFieldValueStr(line, "role:") {
                                info.Fields.PrimaryRole = true
                        }
-               } else if idx := strings.Index(line, "connected_slaves:"); idx != -1 {
-                       cntStr := line[idx+len("connected_slaves:"):]
-                       cnt, err := strconv.ParseUint(cntStr, 10, 32)
-                       if err != nil {
-                               return fmt.Errorf("Info reply error: %s", err.Error())
-                       }
-                       info.Fields.ConnectedReplicaCnt = uint32(cnt)
+               case lineContains(line, "connected_slaves:"):
+                       info.Fields.ConnectedReplicaCnt = getUint32FromString(getFieldValueStr(line, "connected_slaves:"))
+               case lineContains(line, "uptime_in_days:"):
+                       info.Fields.Server.UptimeInDays = getUint32FromString(getFieldValueStr(line, "uptime_in_days:"))
+               case lineContains(line, "connected_clients:"):
+                       info.Fields.Clients.ConnectedClients = getUint32FromString(getFieldValueStr(line, "connected_clients:"))
+               case lineContains(line, "client_recent_max_input_buffer:"):
+                       info.Fields.Clients.ClientRecentMaxInputBuffer = getUint32FromString(getFieldValueStr(line, "client_recent_max_input_buffer:"))
+               case lineContains(line, "client_recent_max_output_buffer:"):
+                       info.Fields.Clients.ClientRecentMaxOutputBuffer = getUint32FromString(getFieldValueStr(line, "client_recent_max_output_buffer:"))
+               case lineContains(line, "used_memory:"):
+                       info.Fields.Memory.UsedMemory = getUint64FromString(getFieldValueStr(line, "used_memory:"))
+               case lineContains(line, "used_memory_human:"):
+                       info.Fields.Memory.UsedMemoryHuman = getFieldValueStr(line, "used_memory_human:")
+               case lineContains(line, "used_memory_rss:"):
+                       info.Fields.Memory.UsedMemoryRss = getUint64FromString(getFieldValueStr(line, "used_memory_rss:"))
+               case lineContains(line, "used_memory_rss_human:"):
+                       info.Fields.Memory.UsedMemoryRssHuman = getFieldValueStr(line, "used_memory_rss_human:")
+               case lineContains(line, "used_memory_peak:"):
+                       info.Fields.Memory.UsedMemoryPeak = getUint64FromString(getFieldValueStr(line, "used_memory_peak:"))
+               case lineContains(line, "used_memory_peak_human:"):
+                       info.Fields.Memory.UsedMemoryPeakHuman = getFieldValueStr(line, "used_memory_peak_human:")
+               case lineContains(line, "used_memory_peak_perc:"):
+                       info.Fields.Memory.UsedMemoryPeakPerc = getFieldValueStr(line, "used_memory_peak_perc:")
+               case lineContains(line, "mem_fragmentation_ratio:"):
+                       info.Fields.Memory.MemFragmentationRatio = getFloat32FromString(getFieldValueStr(line, "mem_fragmentation_ratio:"))
+               case lineContains(line, "mem_fragmentation_bytes:"):
+                       info.Fields.Memory.MemFragmentationBytes = getUint32FromString(getFieldValueStr(line, "mem_fragmentation_bytes:"))
+               case lineContains(line, "total_connections_received:"):
+                       info.Fields.Stats.TotalConnectionsReceived = getUint32FromString(getFieldValueStr(line, "total_connections_received:"))
+               case lineContains(line, "total_commands_processed:"):
+                       info.Fields.Stats.TotalCommandsProcessed = getUint32FromString(getFieldValueStr(line, "total_commands_processed:"))
+               case lineContains(line, "sync_full:"):
+                       info.Fields.Stats.SyncFull = getUint32FromString(getFieldValueStr(line, "sync_full:"))
+               case lineContains(line, "sync_partial_ok:"):
+                       info.Fields.Stats.SyncPartialOk = getUint32FromString(getFieldValueStr(line, "sync_partial_ok:"))
+               case lineContains(line, "sync_partial_err:"):
+                       info.Fields.Stats.SyncPartialErr = getUint32FromString(getFieldValueStr(line, "sync_partial_err:"))
+               case lineContains(line, "pubsub_channels:"):
+                       info.Fields.Stats.PubsubChannels = getUint32FromString(getFieldValueStr(line, "pubsub_channels:"))
+               case lineContains(line, "used_cpu_sys:"):
+                       info.Fields.Cpu.UsedCpuSys = getFloat64FromString(getFieldValueStr(line, "used_cpu_sys:"))
+               case lineContains(line, "used_cpu_user:"):
+                       info.Fields.Cpu.UsedCpuUser = getFloat64FromString(getFieldValueStr(line, "used_cpu_user:"))
+               case lineContains(line, "cmdstat_replconf:"):
+                       updateCommandstatsValues(&info.Fields.Commandstats.CmdstatReplconf, line, "cmdstat_replconf:")
+               case lineContains(line, "cmdstat_keys:"):
+                       updateCommandstatsValues(&info.Fields.Commandstats.CmdstatKeys, line, "cmdstat_keys:")
+               case lineContains(line, "cmdstat_role:"):
+                       updateCommandstatsValues(&info.Fields.Commandstats.CmdstatRole, line, "cmdstat_role:")
+               case lineContains(line, "cmdstat_psync:"):
+                       updateCommandstatsValues(&info.Fields.Commandstats.CmdstatPsync, line, "cmdstat_psync:")
+               case lineContains(line, "cmdstat_mset:"):
+                       updateCommandstatsValues(&info.Fields.Commandstats.CmdstatMset, line, "cmdstat_mset:")
+               case lineContains(line, "cmdstat_publish:"):
+                       updateCommandstatsValues(&info.Fields.Commandstats.CmdstatPublish, line, "cmdstat_publish:")
+               case lineContains(line, "cmdstat_info:"):
+                       updateCommandstatsValues(&info.Fields.Commandstats.CmdstatInfo, line, "cmdstat_info:")
+               case lineContains(line, "cmdstat_ping:"):
+                       updateCommandstatsValues(&info.Fields.Commandstats.CmdstatPing, line, "cmdstat_ping:")
+               case lineContains(line, "cmdstat_client:"):
+                       updateCommandstatsValues(&info.Fields.Commandstats.CmdstatClient, line, "cmdstat_client:")
+               case lineContains(line, "cmdstat_command:"):
+                       updateCommandstatsValues(&info.Fields.Commandstats.CmdstatCommand, line, "cmdstat_command:")
+               case lineContains(line, "cmdstat_subscribe:"):
+                       updateCommandstatsValues(&info.Fields.Commandstats.CmdstatSubscribe, line, "cmdstat_subscribe:")
+               case lineContains(line, "cmdstat_monitor:"):
+                       updateCommandstatsValues(&info.Fields.Commandstats.CmdstatMonitor, line, "cmdstat_monitor:")
+               case lineContains(line, "cmdstat_config:"):
+                       updateCommandstatsValues(&info.Fields.Commandstats.CmdstatConfig, line, "cmdstat_config:")
+               case lineContains(line, "cmdstat_slaveof:"):
+                       updateCommandstatsValues(&info.Fields.Commandstats.CmdstatSlaveof, line, "cmdstat_slaveof:")
+               case lineContains(line, "db0:"):
+                       updateKeyspaceValues(&info.Fields.Keyspace.Db, line, "db0:")
                }
        }
        return nil
@@ -546,6 +709,8 @@ func (db *DB) fillDbStateFromDbInfo(info *DbInfo) (*DbState, error) {
                        PrimaryDbState: PrimaryDbState{
                                Fields: PrimaryDbStateFields{
                                        Role:  "master",
+                                       Ip:    db.cfg.hostname,
+                                       Port:  db.cfg.port,
                                        Flags: "master",
                                },
                        },
@@ -562,6 +727,72 @@ func (db *DB) fillDbStateFromDbInfo(info *DbInfo) (*DbState, error) {
        return &dbState, dbState.Err
 }
 
+func createReplicaDbClient(host string) *DB {
+       cfg := readConfig(osImpl{})
+       cfg.sentinelPort = ""
+       cfg.clusterAddrList, cfg.port, _ = net.SplitHostPort(host)
+       return createDbClient(cfg, cfg.clusterAddrList, newRedisClient, subscribeNotifications, nil)
+}
+
+func getStatisticsInfo(db *DB, host string) (*DbStatisticsInfo, error) {
+       dbStatisticsInfo := new(DbStatisticsInfo)
+       dbStatisticsInfo.IPAddr, dbStatisticsInfo.Port, _ = net.SplitHostPort(host)
+
+       info, err := db.Info()
+       if err != nil {
+               return nil, err
+       }
+       dbStatisticsInfo.Info = info
+
+       return dbStatisticsInfo, nil
+}
+
+func sentinelStatistics(db *DB) (*DbStatistics, error) {
+       dbState := new(DbState)
+       dbStatistics := new(DbStatistics)
+       dbStatisticsInfo := new(DbStatisticsInfo)
+       var err error
+
+       dbState, err = db.State()
+       if err != nil {
+               return nil, err
+       }
+
+       dbStatisticsInfo, err = getStatisticsInfo(db, dbState.PrimaryDbState.GetAddress())
+       dbStatistics.Stats = append(dbStatistics.Stats, dbStatisticsInfo)
+
+       if dbState.ReplicasDbState != nil {
+               for _, r := range dbState.ReplicasDbState.States {
+                       replicaDb := createReplicaDbClient(r.GetAddress())
+                       dbStatisticsInfo, err = getStatisticsInfo(replicaDb, r.GetAddress())
+                       replicaDb.CloseDB()
+                       if err != nil {
+                               return nil, err
+                       }
+                       dbStatistics.Stats = append(dbStatistics.Stats, dbStatisticsInfo)
+               }
+       }
+
+       return dbStatistics, nil
+}
+
+func standaloneStatistics(db *DB) (*DbStatistics, error) {
+       dbStatistics := new(DbStatistics)
+
+       dbStatisticsInfo, err := getStatisticsInfo(db, net.JoinHostPort(db.cfg.hostname, db.cfg.port))
+       dbStatistics.Stats = append(dbStatistics.Stats, dbStatisticsInfo)
+
+       return dbStatistics, err
+}
+
+func (db *DB) Statistics() (*DbStatistics, error) {
+       if db.cfg.sentinelPort != "" {
+               return sentinelStatistics(db)
+       }
+
+       return standaloneStatistics(db)
+}
+
 var luaRefresh = redis.NewScript(`if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("pexpire", KEYS[1], ARGV[2]) else return 0 end`)
 
 func (db *DB) PExpireIE(key string, data interface{}, expiration time.Duration) error {
index 34e057f..112d9cc 100644 (file)
@@ -1207,7 +1207,6 @@ func TestInfoOfStandalonePrimaryRedisSuccessfully(t *testing.T) {
 }
 
 func TestInfoOfStandalonePrimaryRedisFailureWhenIntConversionFails(t *testing.T) {
-       expErr := errors.New("Info reply error: strconv.ParseUint: parsing \"not-int\": invalid syntax")
        _, r, db := setupHaEnv(true)
        redisInfo := "# Replication\r\n" +
                "role:master\r\n" +
@@ -1222,7 +1221,7 @@ func TestInfoOfStandalonePrimaryRedisFailureWhenIntConversionFails(t *testing.T)
 
        r.On("Info", []string{"all"}).Return(redis.NewStringResult(redisInfo, nil))
        info, err := db.Info()
-       assert.Equal(t, expErr, err)
+       assert.Nil(t, err)
        assert.Equal(t, expInfo, info)
        r.AssertExpectations(t)
 }
@@ -1255,6 +1254,71 @@ func TestInfoWithEmptyContentSuccessfully(t *testing.T) {
        r.AssertExpectations(t)
 }
 
+func TestInfoWithSomeStatisticsOfStandalonePrimaryRedis(t *testing.T) {
+       _, r, db := setupHaEnv(true)
+       redisInfo := "# Replication\r\n" +
+               "role:master\r\n" +
+               "connected_slaves:0\r\n" +
+               "min_slaves_good_slaves:0\r\n" +
+               "# Server\r\n" +
+               "uptime_in_days:23\r\n" +
+               "# Clients\r\n" +
+               "connected_clients:2\r\n" +
+               "# Memory\r\n" +
+               "used_memory:2093808\r\n" +
+               "used_memory_human:2.00M\r\n" +
+               "mem_fragmentation_ratio:6.44\r\n" +
+               "# Stats\r\n" +
+               "total_connections_received:278\r\n" +
+               "# CPU\r\n" +
+               "used_cpu_sys:1775.254919\r\n" +
+               "# Commandstats\r\n" +
+               "# cmdstat_role:calls=1,usec=3,usec_per_call=3.00\r\n" +
+               "# Keyspace\r\n" +
+               "db0:keys=4,expires=0,avg_ttl=0"
+       expInfo := &sdlgoredis.DbInfo{
+               Fields: sdlgoredis.DbInfoFields{
+                       PrimaryRole:         true,
+                       ConnectedReplicaCnt: 0,
+                       Server: sdlgoredis.ServerInfoFields{
+                               UptimeInDays: 23,
+                       },
+                       Clients: sdlgoredis.ClientsInfoFields{
+                               ConnectedClients: 2,
+                       },
+                       Memory: sdlgoredis.MeroryInfoFields{
+                               UsedMemory:            2093808,
+                               UsedMemoryHuman:       "2.00M",
+                               MemFragmentationRatio: 6.44,
+                       },
+                       Stats: sdlgoredis.StatsInfoFields{
+                               TotalConnectionsReceived: 278,
+                       },
+                       Cpu: sdlgoredis.CpuInfoFields{
+                               UsedCpuSys: 1775.254919,
+                       },
+                       Commandstats: sdlgoredis.CommandstatsInfoFields{
+                               CmdstatRole: sdlgoredis.CommandstatsValues{
+                                       Calls:       1,
+                                       Usec:        3,
+                                       UsecPerCall: 3.00,
+                               },
+                       },
+                       Keyspace: sdlgoredis.KeyspaceInfoFields{
+                               Db: sdlgoredis.KeyspaceValues{
+                                       Keys: 4,
+                               },
+                       },
+               },
+       }
+
+       r.On("Info", []string{"all"}).Return(redis.NewStringResult(redisInfo, nil))
+       info, err := db.Info()
+       assert.Nil(t, err)
+       assert.Equal(t, expInfo, info)
+       r.AssertExpectations(t)
+}
+
 func TestStateWithPrimaryAndTwoReplicaRedisSuccessfully(t *testing.T) {
        _, r, s, db := setupHaEnvWithSentinels(true, "3")
 
@@ -1402,6 +1466,8 @@ func TestStateWithSinglePrimaryRedisSuccessfully(t *testing.T) {
                PrimaryDbState: sdlgoredis.PrimaryDbState{
                        Fields: sdlgoredis.PrimaryDbStateFields{
                                Role:  "master",
+                               Ip:    "service-ricplt-dbaas-tcp-cluster-0.ricplt",
+                               Port:  "6376",
                                Flags: "master",
                        },
                },
@@ -1428,6 +1494,8 @@ func TestStateWithSinglePrimaryRedisFailureWhenIntConversionFails(t *testing.T)
                PrimaryDbState: sdlgoredis.PrimaryDbState{
                        Fields: sdlgoredis.PrimaryDbStateFields{
                                Role:  "master",
+                               Ip:    "service-ricplt-dbaas-tcp-cluster-0.ricplt",
+                               Port:  "6376",
                                Flags: "master",
                        },
                },
@@ -1456,3 +1524,36 @@ func TestStateWithSinglePrimaryRedisFailureInInfoCall(t *testing.T) {
        assert.Equal(t, expState, ret)
        r.AssertExpectations(t)
 }
+
+func TestStatisticsWithSinglePrimaryRedisSuccessfully(t *testing.T) {
+       _, r, db := setupSingleEnv(true, "1")
+       redisInfo := "# Replication\r\n" +
+               "role:master\r\n" +
+               "connected_slaves:0\r\n" +
+               "min_slaves_good_slaves:0\r\n" +
+               "# Server\r\n" +
+               "uptime_in_days:12\r\n"
+
+       expStatistics := &sdlgoredis.DbStatistics{
+               Stats: []*sdlgoredis.DbStatisticsInfo{
+                       {
+                               IPAddr: "service-ricplt-dbaas-tcp-cluster-0.ricplt",
+                               Port:   "6376",
+                               Info: &sdlgoredis.DbInfo{
+                                       Fields: sdlgoredis.DbInfoFields{
+                                               PrimaryRole: true,
+                                               Server: sdlgoredis.ServerInfoFields{
+                                                       UptimeInDays: 12,
+                                               },
+                                       },
+                               },
+                       },
+               },
+       }
+
+       r.On("Info", []string{"all"}).Return(redis.NewStringResult(redisInfo, nil))
+       ret, err := db.Statistics()
+       assert.Nil(t, err)
+       assert.Equal(t, expStatistics, ret)
+       r.AssertExpectations(t)
+}