EdgexAgent/device-gps-go/internal/cache/devices.go
2025-07-10 20:30:06 +08:00

223 lines
6.0 KiB
Go

// -*- Mode: Go; indent-tabs-mode: t -*-
//
// Copyright (C) 2020-2025 IOTech Ltd
//
// SPDX-License-Identifier: Apache-2.0
package cache
import (
"fmt"
"strings"
"sync"
"time"
bootstrapContainer "github.com/edgexfoundry/go-mod-bootstrap/v4/bootstrap/container"
"github.com/edgexfoundry/go-mod-bootstrap/v4/di"
"github.com/edgexfoundry/go-mod-core-contracts/v4/errors"
"github.com/edgexfoundry/go-mod-core-contracts/v4/models"
gometrics "github.com/rcrowley/go-metrics"
)
const (
deviceNameText = "{DeviceName}"
lastConnectedPrefix = "LastConnected-" + deviceNameText
)
var (
dc *deviceCache
)
type DeviceCache interface {
ForName(name string) (models.Device, bool)
All() []models.Device
Add(device models.Device) errors.EdgeX
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
dic *di.Container
lastConnected map[string]gometrics.Gauge
}
func newDeviceCache(devices []models.Device, dic *di.Container) DeviceCache {
defaultSize := len(devices)
dMap := make(map[string]*models.Device, defaultSize)
dc = &deviceCache{deviceMap: dMap, dic: dic}
lastConnectedMetrics := make(map[string]gometrics.Gauge)
for _, d := range devices {
dMap[d.Name] = &d
deviceMetric := gometrics.NewGauge()
registerMetric(d.Name, deviceMetric, dic)
lastConnectedMetrics[d.Name] = deviceMetric
}
dc.lastConnected = lastConnectedMetrics
return dc
}
func registerMetric(deviceName string, metric interface{}, dic *di.Container) {
metricsManager := bootstrapContainer.MetricsManagerFrom(dic.Get)
lc := bootstrapContainer.LoggingClientFrom(dic.Get)
registeredName := strings.Replace(lastConnectedPrefix, deviceNameText, deviceName, 1)
err := metricsManager.Register(registeredName, metric, map[string]string{"device": deviceName})
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)
}
}
func unregisterMetric(deviceName string, dic *di.Container) {
metricsManager := bootstrapContainer.MetricsManagerFrom(dic.Get)
registeredName := strings.Replace(lastConnectedPrefix, deviceNameText, deviceName, 1)
metricsManager.Unregister(registeredName)
}
// ForName returns a Device with the given device name.
func (d *deviceCache) ForName(name string) (models.Device, bool) {
d.mutex.RLock()
defer d.mutex.RUnlock()
device, ok := d.deviceMap[name]
if !ok {
return models.Device{}, false
}
return *device, ok
}
// All returns the current list of devices in the cache.
func (d *deviceCache) All() []models.Device {
d.mutex.RLock()
defer d.mutex.RUnlock()
i := 0
devices := make([]models.Device, len(d.deviceMap))
for _, device := range d.deviceMap {
devices[i] = *device
i++
}
return devices
}
// Add adds a new device to the cache. This method is used to populate the
// device cache with pre-existing or recently-added devices from Core Metadata.
func (d *deviceCache) Add(device models.Device) errors.EdgeX {
d.mutex.Lock()
defer d.mutex.Unlock()
return d.add(device)
}
func (d *deviceCache) add(device models.Device) errors.EdgeX {
if _, ok := d.deviceMap[device.Name]; ok {
errMsg := fmt.Sprintf("Device %s has already existed in cache", device.Name)
return errors.NewCommonEdgeX(errors.KindDuplicateName, errMsg, nil)
}
d.deviceMap[device.Name] = &device
// register the lastConnected metric for the new added device
deviceMetric := gometrics.NewGauge()
registerMetric(device.Name, deviceMetric, d.dic)
d.lastConnected[device.Name] = deviceMetric
return nil
}
// Update updates the device in the cache
func (d *deviceCache) Update(device models.Device) errors.EdgeX {
d.mutex.Lock()
defer d.mutex.Unlock()
if err := d.removeByName(device.Name); err != nil {
return err
}
return d.add(device)
}
// RemoveByName removes the specified device by name from the cache.
func (d *deviceCache) RemoveByName(name string) errors.EdgeX {
d.mutex.Lock()
defer d.mutex.Unlock()
return d.removeByName(name)
}
func (d *deviceCache) removeByName(name string) errors.EdgeX {
_, ok := d.deviceMap[name]
if !ok {
errMsg := fmt.Sprintf("failed to find Device %s in cache", name)
return errors.NewCommonEdgeX(errors.KindEntityDoesNotExist, errMsg, nil)
}
delete(d.deviceMap, name)
// unregister the lastConnected metric for the removed device
unregisterMetric(name, d.dic)
delete(d.lastConnected, name)
return nil
}
// UpdateAdminState updates the device admin state in cache by name. This method
// is used by the UpdateHandler to trigger update device admin state that's been
// updated directly to Core Metadata.
func (d *deviceCache) UpdateAdminState(name string, state models.AdminState) errors.EdgeX {
if state != models.Locked && state != models.Unlocked {
return errors.NewCommonEdgeX(errors.KindContractInvalid, "invalid AdminState", nil)
}
d.mutex.Lock()
defer d.mutex.Unlock()
_, ok := d.deviceMap[name]
if !ok {
errMsg := fmt.Sprintf("failed to find Device %s in cache", name)
return errors.NewCommonEdgeX(errors.KindEntityDoesNotExist, errMsg, nil)
}
d.deviceMap[name].AdminState = state
return nil
}
func CheckProfileNotUsed(profileName string) bool {
for _, device := range dc.deviceMap {
if device.ProfileName == profileName {
return false
}
}
return true
}
func Devices() DeviceCache {
return dc
}
// currentTimestamp returns the current timestamp in nanoseconds
var currentTimestamp = func() int64 {
return time.Now().UnixNano()
}
func (d *deviceCache) SetLastConnectedByName(name string) {
d.mutex.RLock()
defer d.mutex.RUnlock()
g := d.lastConnected[name]
g.Update(currentTimestamp())
}
func (d *deviceCache) GetLastConnectedByName(name string) int64 {
g := d.lastConnected[name]
return g.Value()
}