86 lines
2.4 KiB
Go
86 lines
2.4 KiB
Go
// Package collector maps Shelly product types to Prometheus collectors.
|
|
package collector
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
"unicode"
|
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
"lostak.dev/shelly-exporter/configuration"
|
|
"lostak.dev/shelly-exporter/shelly"
|
|
)
|
|
|
|
// Factory constructs a collector for one configured device.
|
|
type Factory func(configuration.DeviceConfiguration, shelly.StatusClient) (prometheus.Collector, error)
|
|
|
|
var (
|
|
factoriesMutex sync.RWMutex
|
|
factories = make(map[string]Factory)
|
|
)
|
|
|
|
// RegisterProduct adds support for a product identifier. Product packages call it from init.
|
|
func RegisterProduct(product string, factory Factory) error {
|
|
product = normalizeProduct(product)
|
|
if product == "" {
|
|
return fmt.Errorf("product identifier cannot be empty")
|
|
}
|
|
if factory == nil {
|
|
return fmt.Errorf("factory for product %q cannot be nil", product)
|
|
}
|
|
|
|
factoriesMutex.Lock()
|
|
defer factoriesMutex.Unlock()
|
|
if _, exists := factories[product]; exists {
|
|
return fmt.Errorf("product %q is already registered", product)
|
|
}
|
|
factories[product] = factory
|
|
return nil
|
|
}
|
|
|
|
// NewDeviceCollector creates the registered collector for a configured product.
|
|
func NewDeviceCollector(config configuration.DeviceConfiguration, client shelly.StatusClient) (prometheus.Collector, error) {
|
|
product := normalizeProduct(config.Product)
|
|
|
|
factoriesMutex.RLock()
|
|
factory, exists := factories[product]
|
|
factoriesMutex.RUnlock()
|
|
if !exists {
|
|
return nil, fmt.Errorf("unsupported Shelly product %q (supported: %s)", config.Product, strings.Join(SupportedProducts(), ", "))
|
|
}
|
|
config.Product = product
|
|
return factory(config, client)
|
|
}
|
|
|
|
// SupportedProducts returns all registered identifiers in stable order.
|
|
func SupportedProducts() []string {
|
|
factoriesMutex.RLock()
|
|
defer factoriesMutex.RUnlock()
|
|
|
|
products := make([]string, 0, len(factories))
|
|
for product := range factories {
|
|
products = append(products, product)
|
|
}
|
|
sort.Strings(products)
|
|
return products
|
|
}
|
|
|
|
func normalizeProduct(product string) string {
|
|
var normalized strings.Builder
|
|
separator := false
|
|
for _, character := range strings.ToLower(strings.TrimSpace(product)) {
|
|
if unicode.IsLetter(character) || unicode.IsDigit(character) {
|
|
if separator && normalized.Len() > 0 {
|
|
normalized.WriteByte('_')
|
|
}
|
|
normalized.WriteRune(character)
|
|
separator = false
|
|
} else {
|
|
separator = true
|
|
}
|
|
}
|
|
return normalized.String()
|
|
}
|