Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add possibility for GroupValueRead at startup #56

Merged
merged 8 commits into from
Aug 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,14 @@ Connection:
Endpoint: "192.168.1.15:3671"
PhysicalAddress: 2.0.1
MetricsPrefix: knx_
ReadStartupInterval: 200ms
AddressConfigs:
0/0/1:
Name: dummy_metric
DPT: 1.*
Export: true
MetricType: "counter"
ReadStartup: true
ReadActive: true
MaxAge: 10m
Comment: dummy comment
Expand Down Expand Up @@ -125,6 +127,12 @@ The `MetricsPrefix` defines a single string that will be added to all your expor
format must be compliant with the
[prometheus metrics names](https://prometheus.io/docs/practices/naming/#metric-names).

#### The `ReadStartupInterval`
It is possible to send `GroupValueRead` telegrams to specific group addresses at startup.
Sending out all `GroupValueRead` telegrams at once on startup can overwhelm the KNX bus.
The `ReadStartupInterval` defines the interval between the `GroupValueRead` telegrams sent out at startup.
If not specified, `ReadStartupInterval` is set to 200ms by default.

#### The `AddressConfigs` section

The `AddressConfigs` section defines all the information about the group addresses which should be
Expand All @@ -136,6 +144,7 @@ exported to prometheus. It contains the following structure for every exported g
DPT: 1.*
Export: true
MetricType: "counter"
ReadStartup: true
ReadActive: true
MaxAge: 10m
Comment: dummy comment
Expand Down Expand Up @@ -164,6 +173,9 @@ Next it defines the actual information for a single group address:
- `MetricType` defines the type of the exported metric. Can be either `counter` or `gauge`. See
[Prometheus documentation counter vs. gauge](https://prometheus.io/docs/practices/instrumentation/#counter-vs-gauge-summary-vs-histogram)
for more information about it.
- `ReadStartup` can either be `true` or `false`. If set to `true` the KNX Prometheus Exporter will
send a `GroupValueRead` telegram to the group address to actively ask for a new value once after startup.
In contrast to `ReadActive` this sends out a `GroupValueRead` telegram at startup once.
- `ReadActive` can either be `true` or `false`. If set to `true` the KNX Prometheus Exporter will
send a `GroupValueRead` telegram to the group address to active ask for a new value if the last
received value is older than `MaxAge`.
Expand Down
150 changes: 77 additions & 73 deletions pkg/knx/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,60 +15,62 @@
package knx

import (
"encoding/json"
"fmt"
"os"
"strings"
"time"
"encoding/json"
"fmt"
"os"
"strings"
"time"

"github.com/ghodss/yaml"
"github.com/ghodss/yaml"
)

// Config defines the structure of the configuration file which defines which
// KNX Group Addresses were mapped into prometheus metrics.
type Config struct {
Connection Connection `json:",omitempty"`
// MetricsPrefix is a short prefix which will be added in front of the actual metric name.
MetricsPrefix string
AddressConfigs GroupAddressConfigSet
Connection Connection `json:",omitempty"`
// MetricsPrefix is a short prefix which will be added in front of the actual metric name.
MetricsPrefix string
AddressConfigs GroupAddressConfigSet
// ReadStartupInterval is the intervall to wait between read of group addresses after startup.
ReadStartupInterval Duration `json:",omitempty"`
}

// ReadConfig reads the given configuration file and returns the parsed Config object.
func ReadConfig(configFile string) (*Config, error) {
content, err := os.ReadFile(configFile)
if err != nil {
return nil, fmt.Errorf("can not read group address configuration: %s", err)
}
config := Config{}
err = yaml.Unmarshal(content, &config)
if err != nil {
return nil, fmt.Errorf("can not read config file %s: %s", configFile, err)
}
return &config, nil
content, err := os.ReadFile(configFile)
if err != nil {
return nil, fmt.Errorf("can not read group address configuration: %s", err)
}
config := Config{}
err = yaml.Unmarshal(content, &config)
if err != nil {
return nil, fmt.Errorf("can not read config file %s: %s", configFile, err)
}
return &config, nil
}

// NameForGa returns the full metric name for the given GroupAddress.
func (c *Config) NameForGa(address GroupAddress) string {
gaConfig, ok := c.AddressConfigs[address]
if !ok {
return ""
}
return c.NameFor(gaConfig)
gaConfig, ok := c.AddressConfigs[address]
if !ok {
return ""
}
return c.NameFor(gaConfig)
}

// NameFor return s the full metric name for the given GroupAddressConfig.
func (c *Config) NameFor(gaConfig GroupAddressConfig) string {
return c.MetricsPrefix + gaConfig.Name
return c.MetricsPrefix + gaConfig.Name
}

// Connection contains the information about how to connect to the KNX system and how to identify itself.
type Connection struct {
// Type of the actual connection. Can be either Tunnel or Router
Type ConnectionType
// Endpoint defines the IP address or hostname and port to where it should connect.
Endpoint string
// PhysicalAddress defines how the knx-exporter should identify itself within the KNX system.
PhysicalAddress PhysicalAddress
// Type of the actual connection. Can be either Tunnel or Router
Type ConnectionType
// Endpoint defines the IP address or hostname and port to where it should connect.
Endpoint string
// PhysicalAddress defines how the knx-exporter should identify itself within the KNX system.
PhysicalAddress PhysicalAddress
}

type ConnectionType string
Expand All @@ -77,62 +79,64 @@ const Tunnel = ConnectionType("Tunnel")
const Router = ConnectionType("Router")

func (t ConnectionType) MarshalJSON() ([]byte, error) {
return json.Marshal(string(t))
return json.Marshal(string(t))
}

func (t *ConnectionType) UnmarshalJSON(data []byte) error {
var str string
if err := json.Unmarshal(data, &str); err != nil {
return err
}
switch strings.ToLower(str) {
case "tunnel":
*t = Tunnel
case "router":
*t = Router
default:
return fmt.Errorf("invalid connection type given: \"%s\"", str)
}
return nil
var str string
if err := json.Unmarshal(data, &str); err != nil {
return err
}
switch strings.ToLower(str) {
case "tunnel":
*t = Tunnel
case "router":
*t = Router
default:
return fmt.Errorf("invalid connection type given: \"%s\"", str)
}
return nil
}

type Duration time.Duration

func (d Duration) MarshalJSON() ([]byte, error) {
return json.Marshal(time.Duration(d).String())
return json.Marshal(time.Duration(d).String())
}

func (d *Duration) UnmarshalJSON(data []byte) error {
var str string
if err := json.Unmarshal(data, &str); err != nil {
return err
}
duration, err := time.ParseDuration(str)
if err != nil {
return err
}
*d = Duration(duration)
return nil
var str string
if err := json.Unmarshal(data, &str); err != nil {
return err
}
duration, err := time.ParseDuration(str)
if err != nil {
return err
}
*d = Duration(duration)
return nil
}

// GroupAddressConfig defines all information to map a KNX group address to a prometheus metric.
type GroupAddressConfig struct {
// Name defines the prometheus metric name without the MetricsPrefix.
Name string
// Comment to identify the group address.
Comment string `json:",omitempty"`
// DPT defines the DPT at the knx bus. This is required to parse the values correctly.
DPT string
// MetricType is the type that prometheus uses when exporting it. i.e. gauge or counter
MetricType string
// Export the metric to prometheus
Export bool
// ReadActive allows the exporter to actively send `GroupValueRead` telegrams to actively poll the value instead waiting for it.
ReadActive bool `json:",omitempty"`
// MaxAge of a value until it will actively send a `GroupValueRead` telegram to read the value if ReadActive is set to true.
MaxAge Duration `json:",omitempty"`
// Labels defines static labels that should be set when exporting the metric using prometheus.
Labels map[string]string `json:",omitempty"`
// Name defines the prometheus metric name without the MetricsPrefix.
Name string
// Comment to identify the group address.
Comment string `json:",omitempty"`
// DPT defines the DPT at the knx bus. This is required to parse the values correctly.
DPT string
// MetricType is the type that prometheus uses when exporting it. i.e. gauge or counter
MetricType string
// Export the metric to prometheus
Export bool
// ReadStartup allows the exporter to actively send `GroupValueRead` telegrams to actively read the value at startup instead waiting for it.
ReadStartup bool `json:",omitempty"`
// ReadActive allows the exporter to actively send `GroupValueRead` telegrams to actively poll the value instead waiting for it.
ReadActive bool `json:",omitempty"`
// MaxAge of a value until it will actively send a `GroupValueRead` telegram to read the value if ReadActive is set to true.
MaxAge Duration `json:",omitempty"`
// Labels defines static labels that should be set when exporting the metric using prometheus.
Labels map[string]string `json:",omitempty"`
}

// GroupAddressConfigSet is a shortcut type for the group address config map.
Expand Down
7 changes: 7 additions & 0 deletions pkg/knx/exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ type metricsExporter struct {
metrics MetricSnapshotHandler
listener Listener
messageCounter *prometheus.CounterVec
startupReader StartupReader
poller Poller
health error
}
Expand Down Expand Up @@ -64,6 +65,9 @@ func (e *metricsExporter) Run() error {
return err
}

e.startupReader = NewStartupReader(e.config, e.client, e.metrics, e.messageCounter)
e.startupReader.Run()

e.poller = NewPoller(e.config, e.client, e.metrics, e.messageCounter)
e.poller.Run()

Expand All @@ -75,6 +79,9 @@ func (e *metricsExporter) Run() error {
}

func (e *metricsExporter) Close() {
if e.startupReader != nil {
e.startupReader.Close()
}
if e.poller != nil {
e.poller.Close()
}
Expand Down
5 changes: 5 additions & 0 deletions pkg/knx/fixtures/readConfig.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,37 @@ Connection:
Endpoint: "224.0.0.120:3672"
PhysicalAddress: 2.0.1
MetricsPrefix: knx_
ReadStartupInterval: 250ms
AddressConfigs:
0/0/1:
Name: dummy_metric
DPT: 1.*
Export: true
MetricType: "counter"
ReadStartup: true
ReadActive: true
MaxAge: 5s
0/0/2:
Name: dummy_metric1
DPT: 1.*
Export: true
MetricType: "counter"
ReadStartup: true
ReadActive: true
MaxAge: 5s
0/0/3:
Name: dummy_metric2
DPT: 1.*
Export: true
MetricType: "counter"
ReadStartup: true
ReadActive: true
MaxAge: 5s
0/0/4:
Name: dummy_metric3
DPT: 1.*
Export: true
MetricType: "counter"
ReadStartup: false
ReadActive: false
MaxAge: 5s
Loading