Skip to content

Commit

Permalink
feat: Add device last connected metrics
Browse files Browse the repository at this point in the history
fixes #1470. Add device last connected metrics to Get/Set command.

Signed-off-by: Lindsey Cheng <beckysocute@gmail.com>
  • Loading branch information
lindseysimple committed Sep 20, 2023
1 parent 2684731 commit 99bffe5
Show file tree
Hide file tree
Showing 8 changed files with 128 additions and 12 deletions.
4 changes: 4 additions & 0 deletions internal/application/command.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ func GetCommand(ctx context.Context, deviceName string, commandName string, quer

lc := bootstrapContainer.LoggingClientFrom(dic.Get)
lc.Debugf("GET Device Command successfully. Device: %s, Source: %s, %s: %s", deviceName, commandName, common.CorrelationHeader, utils.FromContext(ctx, common.CorrelationHeader))

cache.Devices().SetLastConnectedByName(deviceName)
return res, nil
}

Expand Down Expand Up @@ -93,6 +95,8 @@ func SetCommand(ctx context.Context, deviceName string, commandName string, quer

lc := bootstrapContainer.LoggingClientFrom(dic.Get)
lc.Debugf("SET Device Command successfully. Device: %s, Source: %s, %s: %s", deviceName, commandName, common.CorrelationHeader, utils.FromContext(ctx, common.CorrelationHeader))

cache.Devices().SetLastConnectedByName(deviceName)
return event, nil
}

Expand Down
60 changes: 56 additions & 4 deletions internal/cache/devices.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,30 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
//
// Copyright (C) 2020-2021 IOTech Ltd
// Copyright (C) 2020-2023 IOTech Ltd
//
// SPDX-License-Identifier: Apache-2.0

package cache

import (
"fmt"
"strings"
"sync"
"time"

bootstrapContainer "github.com/edgexfoundry/go-mod-bootstrap/v3/bootstrap/container"
bootstrapInterfaces "github.com/edgexfoundry/go-mod-bootstrap/v3/bootstrap/interfaces"
"github.com/edgexfoundry/go-mod-bootstrap/v3/di"
"github.com/edgexfoundry/go-mod-core-contracts/v3/clients/logger"
"github.com/edgexfoundry/go-mod-core-contracts/v3/errors"
"github.com/edgexfoundry/go-mod-core-contracts/v3/models"

gometrics "github.com/rcrowley/go-metrics"
)

const (
deviceNameText = "{DeviceName}"
lastConnectedPrefix = "LastConnected-" + deviceNameText
)

var (
Expand All @@ -25,24 +38,50 @@ type DeviceCache interface {
Update(device models.Device) errors.EdgeX
RemoveByName(name string) errors.EdgeX
UpdateAdminState(name string, state models.AdminState) errors.EdgeX
SetLastConnectedByName(name string)
GetLastConnectedByName(name string) int64
}

type deviceCache struct {
deviceMap map[string]*models.Device // key is Device name
mutex sync.RWMutex
deviceMap map[string]*models.Device // key is Device name
mutex sync.RWMutex
lastConnected map[string]gometrics.Gauge
}

func newDeviceCache(devices []models.Device) DeviceCache {
func newDeviceCache(devices []models.Device, dic *di.Container) DeviceCache {
defaultSize := len(devices)
dMap := make(map[string]*models.Device, defaultSize)
for i, d := range devices {
dMap[d.Name] = &devices[i]
}

dc = &deviceCache{deviceMap: dMap}
metricsManager := bootstrapContainer.MetricsManagerFrom(dic.Get)
lc := bootstrapContainer.LoggingClientFrom(dic.Get)
lastConnectedMetrics := registerMetric(metricsManager, devices, lc)
dc.lastConnected = lastConnectedMetrics

return dc
}

func registerMetric(metricManager bootstrapInterfaces.MetricsManager, devices []models.Device, lc logger.LoggingClient) map[string]gometrics.Gauge {
lastConnected := make(map[string]gometrics.Gauge)
metricName := lastConnectedPrefix + ""
for _, d := range devices {
deviceMetric := gometrics.NewGauge()
registeredName := strings.Replace(metricName, deviceNameText, d.Name, 1)

err := metricManager.Register(registeredName, deviceMetric, nil)
if err != nil {
lc.Warnf("Unable to register %s metric. Metric will not be reported : %s", registeredName, err.Error())
} else {
lc.Infof("%s metric has been registered and will be reported (if enabled)", registeredName)
lastConnected[d.Name] = deviceMetric
}
}
return lastConnected
}

// ForName returns a Device with the given device name.
func (d *deviceCache) ForName(name string) (models.Device, bool) {
d.mutex.RLock()
Expand Down Expand Up @@ -152,3 +191,16 @@ func CheckProfileNotUsed(profileName string) bool {
func Devices() DeviceCache {
return dc
}

func (d *deviceCache) SetLastConnectedByName(name string) {
d.mutex.RLock()
defer d.mutex.RUnlock()

g := d.lastConnected[name]
g.Update(time.Now().UnixNano())
}

func (d *deviceCache) GetLastConnectedByName(name string) int64 {
g := d.lastConnected[name]
return g.Value()
}
37 changes: 31 additions & 6 deletions internal/cache/devices_test.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright (C) 2021 IOTech Ltd
// Copyright (C) 2021-2023 IOTech Ltd
//
// SPDX-License-Identifier: Apache-2.0

Expand All @@ -8,8 +8,14 @@ package cache
import (
"testing"

bootstrapContainer "github.com/edgexfoundry/go-mod-bootstrap/v3/bootstrap/container"
"github.com/edgexfoundry/go-mod-bootstrap/v3/bootstrap/interfaces/mocks"
"github.com/edgexfoundry/go-mod-bootstrap/v3/di"
"github.com/edgexfoundry/go-mod-core-contracts/v3/clients/logger"
"github.com/edgexfoundry/go-mod-core-contracts/v3/models"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"
)

Expand All @@ -25,8 +31,23 @@ var newDevice = models.Device{
OperatingState: models.Unlocked,
}

func mockDic() *di.Container {
mockMetricsManager := &mocks.MetricsManager{}
mockMetricsManager.On("Register", mock.Anything, mock.Anything, mock.Anything).Return(nil)
mockMetricsManager.On("Unregister", mock.Anything)
return di.NewContainer(di.ServiceConstructorMap{
bootstrapContainer.MetricsManagerInterfaceName: func(get di.Get) interface{} {
return mockMetricsManager
},
bootstrapContainer.LoggingClientInterfaceName: func(get di.Get) interface{} {
return logger.NewMockClient()
},
})
}

func Test_deviceCache_ForName(t *testing.T) {
newDeviceCache([]models.Device{testDevice})
dic := mockDic()
newDeviceCache([]models.Device{testDevice}, dic)

tests := []struct {
name string
Expand All @@ -48,14 +69,16 @@ func Test_deviceCache_ForName(t *testing.T) {
}

func Test_deviceCache_All(t *testing.T) {
newDeviceCache([]models.Device{testDevice})
dic := mockDic()
newDeviceCache([]models.Device{testDevice}, dic)

res := dc.All()
require.Equal(t, len(res), len(dc.deviceMap))
}

func Test_deviceCache_Add(t *testing.T) {
newDeviceCache([]models.Device{testDevice})
dic := mockDic()
newDeviceCache([]models.Device{testDevice}, dic)

tests := []struct {
name string
Expand All @@ -77,7 +100,8 @@ func Test_deviceCache_Add(t *testing.T) {
}

func Test_deviceCache_RemoveByName(t *testing.T) {
newDeviceCache([]models.Device{testDevice})
dic := mockDic()
newDeviceCache([]models.Device{testDevice}, dic)

tests := []struct {
name string
Expand All @@ -99,7 +123,8 @@ func Test_deviceCache_RemoveByName(t *testing.T) {
}

func Test_deviceCache_UpdateAdminState(t *testing.T) {
newDeviceCache([]models.Device{testDevice})
dic := mockDic()
newDeviceCache([]models.Device{testDevice}, dic)

tests := []struct {
name string
Expand Down
4 changes: 2 additions & 2 deletions internal/cache/init.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
//
// Copyright (C) 2020-2021 IOTech Ltd
// Copyright (C) 2020-2023 IOTech Ltd
//
// SPDX-License-Identifier: Apache-2.0

Expand Down Expand Up @@ -32,7 +32,7 @@ func InitCache(instanceName string, baseServiceName string, dic *di.Container) e
for i := range deviceRes.Devices {
devices[i] = dtos.ToDeviceModel(deviceRes.Devices[i])
}
newDeviceCache(devices)
newDeviceCache(devices, dic)

// init profile cache
profiles := make([]models.DeviceProfile, len(devices))
Expand Down
7 changes: 7 additions & 0 deletions internal/common/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,10 @@ func NewMockDIC() *di.Container {
pwcMock := &clientMocks.ProvisionWatcherClient{}
pwcMock.On("ProvisionWatchersByServiceName", context.Background(), TestDeviceService, 0, -1).Return(responses.MultiProvisionWatchersResponse{}, nil)

mockMetricsManager := &mocks.MetricsManager{}
mockMetricsManager.On("Register", mock.Anything, mock.Anything, mock.Anything).Return(nil)
mockMetricsManager.On("Unregister", mock.Anything)

return di.NewContainer(di.ServiceConstructorMap{
container.ConfigurationName: func(get di.Get) interface{} {
return configuration
Expand All @@ -141,6 +145,9 @@ func NewMockDIC() *di.Container {
bootstrapContainer.ProvisionWatcherClientName: func(get di.Get) interface{} {
return pwcMock
},
bootstrapContainer.MetricsManagerInterfaceName: func(get di.Get) interface{} {
return mockMetricsManager
},
})
}

Expand Down
9 changes: 9 additions & 0 deletions internal/controller/http/command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"testing"

bootstrapContainer "github.com/edgexfoundry/go-mod-bootstrap/v3/bootstrap/container"
bootstrapMocks "github.com/edgexfoundry/go-mod-bootstrap/v3/bootstrap/interfaces/mocks"
"github.com/edgexfoundry/go-mod-bootstrap/v3/di"
clientMocks "github.com/edgexfoundry/go-mod-core-contracts/v3/clients/interfaces/mocks"
"github.com/edgexfoundry/go-mod-core-contracts/v3/clients/logger"
Expand Down Expand Up @@ -170,6 +171,11 @@ func mockDic() *di.Container {
mockDriver.On("HandleReadCommands", driverErrorDevice, mock.Anything, mock.Anything).Return(nil, errors.New("ProtocolDriver returned error"))
mockDriver.On("HandleWriteCommands", testDevice, mock.Anything, mock.Anything, mock.Anything).Return(nil)
mockDriver.On("HandleWriteCommands", driverErrorDevice, mock.Anything, mock.Anything, mock.Anything).Return(errors.New("ProtocolDriver returned error"))

mockMetricsManager := &bootstrapMocks.MetricsManager{}
mockMetricsManager.On("Register", mock.Anything, mock.Anything, mock.Anything).Return(nil)
mockMetricsManager.On("Unregister", mock.Anything)

dic := di.NewContainer(di.ServiceConstructorMap{
container.ConfigurationName: func(get di.Get) any {
return &config.ConfigurationStruct{
Expand Down Expand Up @@ -199,6 +205,9 @@ func mockDic() *di.Container {
AdminState: models.Unlocked,
}
},
bootstrapContainer.MetricsManagerInterfaceName: func(get di.Get) interface{} {
return mockMetricsManager
},
})

return dic
Expand Down
10 changes: 10 additions & 0 deletions internal/provision/mockdic_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// -*- Mode: Go; indent-tabs-mode: t -*-
//
// # Copyright (C) 2023 Intel Corporation
// # Copyright (C) 2023 IOTech Ltd
//
// SPDX-License-Identifier: Apache-2.0
package provision
Expand All @@ -10,12 +11,14 @@ import (
"github.com/edgexfoundry/device-sdk-go/v3/internal/config"
"github.com/edgexfoundry/device-sdk-go/v3/internal/container"
bootstrapContainer "github.com/edgexfoundry/go-mod-bootstrap/v3/bootstrap/container"
bootstrapMocks "github.com/edgexfoundry/go-mod-bootstrap/v3/bootstrap/interfaces/mocks"
"github.com/edgexfoundry/go-mod-bootstrap/v3/di"
clientMocks "github.com/edgexfoundry/go-mod-core-contracts/v3/clients/interfaces/mocks"
"github.com/edgexfoundry/go-mod-core-contracts/v3/clients/logger"
"github.com/edgexfoundry/go-mod-core-contracts/v3/dtos"
"github.com/edgexfoundry/go-mod-core-contracts/v3/dtos/responses"
"github.com/edgexfoundry/go-mod-core-contracts/v3/models"
"github.com/stretchr/testify/mock"
)

const (
Expand Down Expand Up @@ -96,6 +99,10 @@ func NewMockDIC() (*di.Container, *clientMocks.DeviceProfileClient) {
pwcMock := &clientMocks.ProvisionWatcherClient{}
pwcMock.On("ProvisionWatchersByServiceName", context.Background(), TestDeviceService, 0, -1).Return(responses.MultiProvisionWatchersResponse{}, nil)

mockMetricsManager := &bootstrapMocks.MetricsManager{}
mockMetricsManager.On("Register", mock.Anything, mock.Anything, mock.Anything).Return(nil)
mockMetricsManager.On("Unregister", mock.Anything)

return di.NewContainer(di.ServiceConstructorMap{
container.ConfigurationName: func(get di.Get) interface{} {
return configuration
Expand All @@ -115,5 +122,8 @@ func NewMockDIC() (*di.Container, *clientMocks.DeviceProfileClient) {
bootstrapContainer.ProvisionWatcherClientName: func(get di.Get) interface{} {
return pwcMock
},
bootstrapContainer.MetricsManagerInterfaceName: func(get di.Get) interface{} {
return mockMetricsManager
},
}), dpcMock
}
9 changes: 9 additions & 0 deletions internal/transformer/transform_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"testing"

bootstrapContainer "github.com/edgexfoundry/go-mod-bootstrap/v3/bootstrap/container"
bootstrapMocks "github.com/edgexfoundry/go-mod-bootstrap/v3/bootstrap/interfaces/mocks"
"github.com/edgexfoundry/go-mod-bootstrap/v3/di"
clientMocks "github.com/edgexfoundry/go-mod-core-contracts/v3/clients/interfaces/mocks"
"github.com/edgexfoundry/go-mod-core-contracts/v3/clients/logger"
Expand All @@ -21,6 +22,7 @@ import (
"github.com/edgexfoundry/go-mod-core-contracts/v3/dtos/responses"
"github.com/edgexfoundry/go-mod-core-contracts/v3/models"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"github.com/stretchr/testify/require"

"github.com/edgexfoundry/device-sdk-go/v3/internal/cache"
Expand Down Expand Up @@ -109,6 +111,10 @@ func NewMockDIC() *di.Container {
Device: config.DeviceInfo{MaxCmdOps: 1},
}

mockMetricsManager := &bootstrapMocks.MetricsManager{}
mockMetricsManager.On("Register", mock.Anything, mock.Anything, mock.Anything).Return(nil)
mockMetricsManager.On("Unregister", mock.Anything)

return di.NewContainer(di.ServiceConstructorMap{
bootstrapContainer.LoggingClientInterfaceName: func(get di.Get) interface{} {
return logger.NewMockClient()
Expand All @@ -128,6 +134,9 @@ func NewMockDIC() *di.Container {
container.ConfigurationName: func(get di.Get) interface{} {
return configuration
},
bootstrapContainer.MetricsManagerInterfaceName: func(get di.Get) interface{} {
return mockMetricsManager
},
})
}
func Test_getUniqueOrigin(t *testing.T) {
Expand Down

0 comments on commit 99bffe5

Please sign in to comment.