diff --git a/.golangci.yml b/.golangci.yml index 3063366..234448a 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -15,6 +15,7 @@ linters: - wsl_v5 - gomodguard_v2 disable: + - goconst - wsl - gomodguard - dupl diff --git a/internal/collector/api.go b/internal/collector/api.go index 9ded830..1308733 100644 --- a/internal/collector/api.go +++ b/internal/collector/api.go @@ -9,22 +9,35 @@ import ( ) type Icinga2APICollector struct { - icingaClient icinga.IcingaClient - logger *slog.Logger - api_num_conn_endpoints *prometheus.Desc - api_num_not_conn_endpoints *prometheus.Desc - api_num_endpoints *prometheus.Desc - api_num_http_clients *prometheus.Desc + icingaClient icinga.IcingaClient + logger *slog.Logger + api_num_conn_endpoints *prometheus.Desc + api_num_not_conn_endpoints *prometheus.Desc + api_num_endpoints *prometheus.Desc + api_num_http_clients *prometheus.Desc + api_jsonrpc_anonymous_clients *prometheus.Desc + api_jsonrpc_relay_queue_item_rate *prometheus.Desc + api_jsonrpc_relay_queue_items *prometheus.Desc + api_jsonrpc_sync_queue_item_rate *prometheus.Desc + api_jsonrpc_sync_queue_items *prometheus.Desc + api_jsonrpc_work_queue_item_rate *prometheus.Desc } func NewIcinga2APICollector(client icinga.IcingaClient, logger *slog.Logger) *Icinga2APICollector { return &Icinga2APICollector{ icingaClient: client, logger: logger, - api_num_conn_endpoints: prometheus.NewDesc("icinga2_api_num_conn_endpoints", "Number of connected Endpoints", nil, nil), - api_num_endpoints: prometheus.NewDesc("icinga2_api_num_endpoints", "Number of Endpoints", nil, nil), - api_num_not_conn_endpoints: prometheus.NewDesc("icinga2_api_num_not_conn_endpoints", "Number of not connected Endpoints", nil, nil), - api_num_http_clients: prometheus.NewDesc("icinga2_api_num_http_clients", "Number of HTTP Clients", nil, nil), + api_num_conn_endpoints: prometheus.NewDesc("icinga2_api_num_conn_endpoints", "Number of connected endpoints", nil, nil), + api_num_endpoints: prometheus.NewDesc("icinga2_api_num_endpoints", "Number of endpoints", nil, nil), + api_num_not_conn_endpoints: prometheus.NewDesc("icinga2_api_num_not_conn_endpoints", "Number of not connected endpoints", nil, nil), + api_num_http_clients: prometheus.NewDesc("icinga2_api_num_http_clients", "Number of HTTP clients", nil, nil), + + api_jsonrpc_anonymous_clients: prometheus.NewDesc("icinga2_api_jsonrpc_anonymous_clients", "JSON RPC anonymous clients", nil, nil), + api_jsonrpc_relay_queue_item_rate: prometheus.NewDesc("icinga2_api_jsonrpc_relay_queue_item_rate", "JSON RPC relay queue item rate", nil, nil), + api_jsonrpc_relay_queue_items: prometheus.NewDesc("icinga2_api_jsonrpc_relay_queue_items", "JSON RPC relay queue items", nil, nil), + api_jsonrpc_sync_queue_item_rate: prometheus.NewDesc("icinga2_api_jsonrpc_sync_queue_item_rate", "JSON RPC sync queue item rate", nil, nil), + api_jsonrpc_sync_queue_items: prometheus.NewDesc("icinga2_api_jsonrpc_sync_queue_items", "JSON RPC sync queue items", nil, nil), + api_jsonrpc_work_queue_item_rate: prometheus.NewDesc("icinga2_api_jsonrpc_work_queue_item_rate", "JSON RPC work queue item rate", nil, nil), } } @@ -36,31 +49,52 @@ func (collector *Icinga2APICollector) Describe(ch chan<- *prometheus.Desc) { ch <- collector.api_num_endpoints ch <- collector.api_num_http_clients + + ch <- collector.api_jsonrpc_anonymous_clients + + ch <- collector.api_jsonrpc_relay_queue_item_rate + + ch <- collector.api_jsonrpc_relay_queue_items + + ch <- collector.api_jsonrpc_sync_queue_item_rate + + ch <- collector.api_jsonrpc_sync_queue_items + + ch <- collector.api_jsonrpc_work_queue_item_rate } func (collector *Icinga2APICollector) Collect(ch chan<- prometheus.Metric) { - perfdata, err := collector.icingaClient.GetPerfdataMetrics(icinga.EndpointApiListener) + result, err := collector.icingaClient.GetAPIMetrics() if err != nil { collector.logger.Error("Could not retrieve ApiListener metrics", "error", err.Error()) return } - for _, datapoint := range perfdata { - if datapoint.Label == "api_num_conn_endpoints" { - ch <- prometheus.MustNewConstMetric(collector.api_num_conn_endpoints, prometheus.GaugeValue, datapoint.Value) - } + if len(result.Results) < 1 { + collector.logger.Debug("No results for ApiListener metrics") + return + } + + r := result.Results[0] - if datapoint.Label == "api_num_not_conn_endpoints" { - ch <- prometheus.MustNewConstMetric(collector.api_num_not_conn_endpoints, prometheus.GaugeValue, datapoint.Value) - } + ch <- prometheus.MustNewConstMetric(collector.api_num_conn_endpoints, prometheus.GaugeValue, r.Status.API.NumConnEndpoints) - if datapoint.Label == "api_num_endpoints" { - ch <- prometheus.MustNewConstMetric(collector.api_num_endpoints, prometheus.GaugeValue, datapoint.Value) - } + ch <- prometheus.MustNewConstMetric(collector.api_num_not_conn_endpoints, prometheus.GaugeValue, r.Status.API.NumNotConnEndpoints) - if datapoint.Label == "api_num_http_clients" { - ch <- prometheus.MustNewConstMetric(collector.api_num_http_clients, prometheus.GaugeValue, datapoint.Value) - } - } + ch <- prometheus.MustNewConstMetric(collector.api_num_endpoints, prometheus.GaugeValue, r.Status.API.NumEndpoints) + + ch <- prometheus.MustNewConstMetric(collector.api_num_http_clients, prometheus.GaugeValue, r.Status.API.HTTP.Clients) + + ch <- prometheus.MustNewConstMetric(collector.api_jsonrpc_anonymous_clients, prometheus.GaugeValue, r.Status.API.JSONRPC.AnonymousClients) + + ch <- prometheus.MustNewConstMetric(collector.api_jsonrpc_relay_queue_item_rate, prometheus.GaugeValue, r.Status.API.JSONRPC.SyncQueueItemRate) + + ch <- prometheus.MustNewConstMetric(collector.api_jsonrpc_relay_queue_items, prometheus.GaugeValue, r.Status.API.JSONRPC.RelayQueueItems) + + ch <- prometheus.MustNewConstMetric(collector.api_jsonrpc_sync_queue_item_rate, prometheus.GaugeValue, r.Status.API.JSONRPC.SyncQueueItemRate) + + ch <- prometheus.MustNewConstMetric(collector.api_jsonrpc_sync_queue_items, prometheus.GaugeValue, r.Status.API.JSONRPC.SyncQueueItems) + + ch <- prometheus.MustNewConstMetric(collector.api_jsonrpc_work_queue_item_rate, prometheus.GaugeValue, r.Status.API.JSONRPC.WorkQueueItemRate) } diff --git a/internal/collector/api_test.go b/internal/collector/api_test.go new file mode 100644 index 0000000..29755d2 --- /dev/null +++ b/internal/collector/api_test.go @@ -0,0 +1,48 @@ +package collector + +import ( + "encoding/json" + "io" + "log/slog" + "os" + "strings" + "testing" + + "github.com/NETWAYS/icinga2-exporter/internal/icinga" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/testutil" +) + +func TestIcinga2APICollector_Collect(t *testing.T) { + client := &MockIcingaClient{} + + var result icinga.APIResult + data, _ := os.ReadFile("testdata/api.json") + json.Unmarshal(data, &result) + + client.SetAPIMetrics(result) + + logger := slog.New(slog.NewTextHandler(io.Discard, nil)) + collector := NewIcinga2APICollector(client, logger) + + reg := prometheus.NewRegistry() + reg.MustRegister(collector) + + metricCount := testutil.CollectAndCount(collector) + + expectedCount := 10 + if metricCount != expectedCount { + t.Errorf("expected %d metrics, got %d", expectedCount, metricCount) + } + + expectedMetrics := ` + # HELP icinga2_api_jsonrpc_sync_queue_item_rate JSON RPC sync queue item rate + # TYPE icinga2_api_jsonrpc_sync_queue_item_rate gauge + icinga2_api_jsonrpc_sync_queue_item_rate 3 + ` + + if err := testutil.CollectAndCompare(collector, strings.NewReader(expectedMetrics), "icinga2_api_jsonrpc_sync_queue_item_rate"); err != nil { + t.Errorf("unexpected metric difference:\n%s", err) + } +} diff --git a/internal/collector/checker.go b/internal/collector/checker.go index 9c17a11..ee37095 100644 --- a/internal/collector/checker.go +++ b/internal/collector/checker.go @@ -31,20 +31,21 @@ func (collector *Icinga2CheckerCollector) Describe(ch chan<- *prometheus.Desc) { } func (collector *Icinga2CheckerCollector) Collect(ch chan<- prometheus.Metric) { - perfdata, err := collector.icingaClient.GetPerfdataMetrics(icinga.EndpointCheckerComponent) + result, err := collector.icingaClient.GetCheckerComponentMetrics() if err != nil { collector.logger.Error("Could not retrieve CheckerComponent metrics", "error", err.Error()) return } - for _, datapoint := range perfdata { - if datapoint.Label == "checkercomponent_checker_idle" { - ch <- prometheus.MustNewConstMetric(collector.checkercomponent_checker_idle, prometheus.GaugeValue, datapoint.Value) - } - - if datapoint.Label == "checkercomponent_checker_pending" { - ch <- prometheus.MustNewConstMetric(collector.checkercomponent_checker_pending, prometheus.GaugeValue, datapoint.Value) - } + if len(result.Results) < 1 { + collector.logger.Debug("No results for CheckerComponent metrics") + return } + + r := result.Results[0] + + ch <- prometheus.MustNewConstMetric(collector.checkercomponent_checker_idle, prometheus.GaugeValue, r.Status.CheckerComponent.Checker.Idle) + + ch <- prometheus.MustNewConstMetric(collector.checkercomponent_checker_pending, prometheus.GaugeValue, r.Status.CheckerComponent.Checker.Pending) } diff --git a/internal/collector/checker_test.go b/internal/collector/checker_test.go new file mode 100644 index 0000000..d2819eb --- /dev/null +++ b/internal/collector/checker_test.go @@ -0,0 +1,48 @@ +package collector + +import ( + "encoding/json" + "io" + "log/slog" + "os" + "strings" + "testing" + + "github.com/NETWAYS/icinga2-exporter/internal/icinga" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/testutil" +) + +func TestIcinga2CheckerCollector_Collect(t *testing.T) { + client := &MockIcingaClient{} + + var result icinga.CheckerComponentResult + data, _ := os.ReadFile("testdata/checker.json") + json.Unmarshal(data, &result) + + client.SetCheckerComponentMetrics(result) + + logger := slog.New(slog.NewTextHandler(io.Discard, nil)) + collector := NewIcinga2CheckerCollector(client, logger) + + reg := prometheus.NewRegistry() + reg.MustRegister(collector) + + metricCount := testutil.CollectAndCount(collector) + + expectedCount := 2 + if metricCount != expectedCount { + t.Errorf("expected %d metrics, got %d", expectedCount, metricCount) + } + + expectedMetrics := ` + # HELP icinga2_checkercomponent_checker_pending CheckerComponent pending + # TYPE icinga2_checkercomponent_checker_pending gauge + icinga2_checkercomponent_checker_pending 41 + ` + + if err := testutil.CollectAndCompare(collector, strings.NewReader(expectedMetrics), "icinga2_checkercomponent_checker_pending"); err != nil { + t.Errorf("unexpected metric difference:\n%s", err) + } +} diff --git a/internal/collector/graphite.go b/internal/collector/graphite.go index 39a31be..f8c3c62 100644 --- a/internal/collector/graphite.go +++ b/internal/collector/graphite.go @@ -1,6 +1,7 @@ package collector import ( + "fmt" "log/slog" "github.com/NETWAYS/icinga2-exporter/internal/icinga" @@ -9,50 +10,51 @@ import ( ) type Icinga2GraphiteCollector struct { - icingaClient icinga.IcingaClient - logger *slog.Logger - graphitewriter_graphite_data_queue_items *prometheus.Desc - graphitewriter_graphite_work_queue_items *prometheus.Desc - graphitewriter_graphite_work_queue_item_rate *prometheus.Desc + icingaClient icinga.IcingaClient + logger *slog.Logger } func NewIcinga2GraphiteCollector(client icinga.IcingaClient, logger *slog.Logger) *Icinga2GraphiteCollector { return &Icinga2GraphiteCollector{ - icingaClient: client, - logger: logger, - graphitewriter_graphite_data_queue_items: prometheus.NewDesc("icinga2_graphitewriter_graphite_data_queue_items", "GraphiteWriter data queue items", nil, nil), - graphitewriter_graphite_work_queue_items: prometheus.NewDesc("icinga2_graphitewriter_graphite_work_queue_items", "GraphiteWriter work queue items", nil, nil), - graphitewriter_graphite_work_queue_item_rate: prometheus.NewDesc("icinga2_graphitewriter_graphite_work_queue_item_rate", "GraphiteWriter work queue item rate", nil, nil), + icingaClient: client, + logger: logger, } } func (collector *Icinga2GraphiteCollector) Describe(ch chan<- *prometheus.Desc) { - ch <- collector.graphitewriter_graphite_data_queue_items - - ch <- collector.graphitewriter_graphite_work_queue_items - - ch <- collector.graphitewriter_graphite_work_queue_item_rate } func (collector *Icinga2GraphiteCollector) Collect(ch chan<- prometheus.Metric) { perfdata, err := collector.icingaClient.GetPerfdataMetrics(icinga.EndpointGraphiteWriter) if err != nil { - collector.logger.Error("Could not retrieve Graphite metrics", "error", err.Error()) + collector.logger.Error("Could not retrieve GraphiteWriter metrics", "error", err.Error()) return } - for _, datapoint := range perfdata { - if datapoint.Label == "graphitewriter_graphite_data_queue_items" { - ch <- prometheus.MustNewConstMetric(collector.graphitewriter_graphite_data_queue_items, prometheus.GaugeValue, datapoint.Value) - } + for _, result := range perfdata.Results { + for component, writers := range result.Status { + for writer, metrics := range writers { + for metricName, value := range metrics { + if !isPerfdataMetric(metricName) { + continue + } - if datapoint.Label == "graphitewriter_graphite_work_queue_items" { - ch <- prometheus.MustNewConstMetric(collector.graphitewriter_graphite_work_queue_items, prometheus.GaugeValue, datapoint.Value) - } + safeMetricName := ensureValidMetricName(metricName) + + name := fmt.Sprintf("icinga2_%s_%s", component, safeMetricName) + + description := prometheus.NewDesc(name, "GraphiteWriter "+name, []string{"writer"}, nil) + metric, err := prometheus.NewConstMetric(description, prometheus.GaugeValue, value, writer) + + if err != nil { + collector.logger.Error("Error creating metric "+metricName, "error", err.Error()) + continue + } - if datapoint.Label == "graphitewriter_graphite_work_queue_item_rate" { - ch <- prometheus.MustNewConstMetric(collector.graphitewriter_graphite_work_queue_item_rate, prometheus.GaugeValue, datapoint.Value) + ch <- metric + } + } } } } diff --git a/internal/collector/graphite_test.go b/internal/collector/graphite_test.go new file mode 100644 index 0000000..a0c1465 --- /dev/null +++ b/internal/collector/graphite_test.go @@ -0,0 +1,48 @@ +package collector + +import ( + "encoding/json" + "io" + "log/slog" + "os" + "strings" + "testing" + + "github.com/NETWAYS/icinga2-exporter/internal/icinga" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/testutil" +) + +func TestIcinga2GraphiteWriterCollector_Collect(t *testing.T) { + client := &MockIcingaClient{} + + var result icinga.PerfdataResult + data, _ := os.ReadFile("testdata/graphite.json") + json.Unmarshal(data, &result) + + client.SetPerfdataMetrics(result) + + logger := slog.New(slog.NewTextHandler(io.Discard, nil)) + collector := NewIcinga2GraphiteCollector(client, logger) + + reg := prometheus.NewRegistry() + reg.MustRegister(collector) + + metricCount := testutil.CollectAndCount(collector) + + expectedCount := 2 + if metricCount != expectedCount { + t.Errorf("expected %d metrics, got %d", expectedCount, metricCount) + } + + expectedMetrics := ` + # HELP icinga2_graphitewriter_work_queue_items GraphiteWriter icinga2_graphitewriter_work_queue_items + # TYPE icinga2_graphitewriter_work_queue_items gauge + icinga2_graphitewriter_work_queue_items{writer="graphite"} 2 + ` + + if err := testutil.CollectAndCompare(collector, strings.NewReader(expectedMetrics), "icinga2_graphitewriter_work_queue_items"); err != nil { + t.Errorf("unexpected metric difference:\n%s", err) + } +} diff --git a/internal/collector/influxdb.go b/internal/collector/influxdb.go index ed78b99..15b1911 100644 --- a/internal/collector/influxdb.go +++ b/internal/collector/influxdb.go @@ -1,6 +1,7 @@ package collector import ( + "fmt" "log/slog" "github.com/NETWAYS/icinga2-exporter/internal/icinga" @@ -9,50 +10,51 @@ import ( ) type Icinga2InfluxDBCollector struct { - icingaClient icinga.IcingaClient - logger *slog.Logger - influxdbwriter_influxdb_work_queue_items *prometheus.Desc - influxdbwriter_influxdb_work_queue_item_rate *prometheus.Desc - influxdbwriter_influxdb_data_queue_items *prometheus.Desc + icingaClient icinga.IcingaClient + logger *slog.Logger } func NewIcinga2InfluxDBCollector(client icinga.IcingaClient, logger *slog.Logger) *Icinga2InfluxDBCollector { return &Icinga2InfluxDBCollector{ - icingaClient: client, - logger: logger, - influxdbwriter_influxdb_work_queue_items: prometheus.NewDesc("icinga2_influxdbwriter_influxdb_work_queue_items", "InfluxDBWriter work queue items", nil, nil), - influxdbwriter_influxdb_work_queue_item_rate: prometheus.NewDesc("icinga2_influxdbwriter_influxdb_work_queue_item_rate", "InfluxDBWriter work queue item rate", nil, nil), - influxdbwriter_influxdb_data_queue_items: prometheus.NewDesc("icinga2_influxdbwriter_influxdb_data_queue_items", "InfluxDBWriter data queue items", nil, nil), + icingaClient: client, + logger: logger, } } func (collector *Icinga2InfluxDBCollector) Describe(ch chan<- *prometheus.Desc) { - ch <- collector.influxdbwriter_influxdb_work_queue_items - - ch <- collector.influxdbwriter_influxdb_work_queue_item_rate - - ch <- collector.influxdbwriter_influxdb_data_queue_items } func (collector *Icinga2InfluxDBCollector) Collect(ch chan<- prometheus.Metric) { perfdata, err := collector.icingaClient.GetPerfdataMetrics(icinga.EndpointInfluxdbWriter) if err != nil { - collector.logger.Error("Could not retrieve InfluxDB metrics", "error", err.Error()) + collector.logger.Error("Could not retrieve InfluxDBWriter metrics", "error", err.Error()) return } - for _, datapoint := range perfdata { - if datapoint.Label == "influxdbwriter_influxdb_work_queue_items" { - ch <- prometheus.MustNewConstMetric(collector.influxdbwriter_influxdb_work_queue_items, prometheus.GaugeValue, datapoint.Value) - } + for _, result := range perfdata.Results { + for component, writers := range result.Status { + for writer, metrics := range writers { + for metricName, value := range metrics { + if !isPerfdataMetric(metricName) { + continue + } - if datapoint.Label == "influxdbwriter_influxdb_work_queue_item_rate" { - ch <- prometheus.MustNewConstMetric(collector.influxdbwriter_influxdb_work_queue_item_rate, prometheus.GaugeValue, datapoint.Value) - } + safeMetricName := ensureValidMetricName(metricName) + + name := fmt.Sprintf("icinga2_%s_%s", component, safeMetricName) + + description := prometheus.NewDesc(name, "InfluxDBWriter "+name, []string{"writer"}, nil) + metric, err := prometheus.NewConstMetric(description, prometheus.GaugeValue, value, writer) + + if err != nil { + collector.logger.Error("Error creating metric "+metricName, "error", err.Error()) + continue + } - if datapoint.Label == "influxdbwriter_influxdb_data_queue_items" { - ch <- prometheus.MustNewConstMetric(collector.influxdbwriter_influxdb_data_queue_items, prometheus.GaugeValue, datapoint.Value) + ch <- metric + } + } } } } diff --git a/internal/collector/influxdb2.go b/internal/collector/influxdb2.go index 44d8acb..4b3d957 100644 --- a/internal/collector/influxdb2.go +++ b/internal/collector/influxdb2.go @@ -1,6 +1,7 @@ package collector import ( + "fmt" "log/slog" "github.com/NETWAYS/icinga2-exporter/internal/icinga" @@ -9,50 +10,51 @@ import ( ) type Icinga2InfluxDB2Collector struct { - icingaClient icinga.IcingaClient - logger *slog.Logger - influxdb2writer_influxdb2_work_queue_items *prometheus.Desc - influxdb2writer_influxdb2_work_queue_item_rate *prometheus.Desc - influxdb2writer_influxdb2_data_queue_items *prometheus.Desc + icingaClient icinga.IcingaClient + logger *slog.Logger } func NewIcinga2InfluxDB2Collector(client icinga.IcingaClient, logger *slog.Logger) *Icinga2InfluxDB2Collector { return &Icinga2InfluxDB2Collector{ icingaClient: client, logger: logger, - influxdb2writer_influxdb2_work_queue_items: prometheus.NewDesc("icinga2_influxdb2writer_influxdb2_work_queue_items", "InfluxDB2Writer work queue items", nil, nil), - influxdb2writer_influxdb2_work_queue_item_rate: prometheus.NewDesc("icinga2_influxdb2writer_influxdb2_work_queue_item_rate", "InfluxDB2Writer work queue item rate", nil, nil), - influxdb2writer_influxdb2_data_queue_items: prometheus.NewDesc("icinga2_influxdb2writer_influxdb2_data_queue_items", "InfluxDB2Writer data queue items", nil, nil), } } func (collector *Icinga2InfluxDB2Collector) Describe(ch chan<- *prometheus.Desc) { - ch <- collector.influxdb2writer_influxdb2_work_queue_items - - ch <- collector.influxdb2writer_influxdb2_work_queue_item_rate - - ch <- collector.influxdb2writer_influxdb2_data_queue_items } func (collector *Icinga2InfluxDB2Collector) Collect(ch chan<- prometheus.Metric) { perfdata, err := collector.icingaClient.GetPerfdataMetrics(icinga.EndpointInfluxdb2Writer) if err != nil { - collector.logger.Error("Could not retrieve InfluxDB2 metrics", "error", err.Error()) + collector.logger.Error("Could not retrieve InfluxDB2Writer metrics", "error", err.Error()) return } - for _, datapoint := range perfdata { - if datapoint.Label == "influxdb2writer_influxdb2_work_queue_items" { - ch <- prometheus.MustNewConstMetric(collector.influxdb2writer_influxdb2_work_queue_items, prometheus.GaugeValue, datapoint.Value) - } + for _, result := range perfdata.Results { + for component, writers := range result.Status { + for writer, metrics := range writers { + for metricName, value := range metrics { + if !isPerfdataMetric(metricName) { + continue + } - if datapoint.Label == "influxdb2writer_influxdb2_work_queue_item_rate" { - ch <- prometheus.MustNewConstMetric(collector.influxdb2writer_influxdb2_work_queue_item_rate, prometheus.GaugeValue, datapoint.Value) - } + safeMetricName := ensureValidMetricName(metricName) + + name := fmt.Sprintf("icinga2_%s_%s", component, safeMetricName) + + description := prometheus.NewDesc(name, "InfluxDB2Writer "+name, []string{"writer"}, nil) + metric, err := prometheus.NewConstMetric(description, prometheus.GaugeValue, value, writer) + + if err != nil { + collector.logger.Error("Error creating metric "+metricName, "error", err.Error()) + continue + } - if datapoint.Label == "influxdb2writer_influxdb2_data_queue_items" { - ch <- prometheus.MustNewConstMetric(collector.influxdb2writer_influxdb2_data_queue_items, prometheus.GaugeValue, datapoint.Value) + ch <- metric + } + } } } } diff --git a/internal/collector/influxdb2_test.go b/internal/collector/influxdb2_test.go new file mode 100644 index 0000000..8705f8e --- /dev/null +++ b/internal/collector/influxdb2_test.go @@ -0,0 +1,48 @@ +package collector + +import ( + "encoding/json" + "io" + "log/slog" + "os" + "strings" + "testing" + + "github.com/NETWAYS/icinga2-exporter/internal/icinga" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/testutil" +) + +func TestIcinga2InfluxDB2WriterCollector_Collect(t *testing.T) { + client := &MockIcingaClient{} + + var result icinga.PerfdataResult + data, _ := os.ReadFile("testdata/influx2.json") + json.Unmarshal(data, &result) + + client.SetPerfdataMetrics(result) + + logger := slog.New(slog.NewTextHandler(io.Discard, nil)) + collector := NewIcinga2InfluxDB2Collector(client, logger) + + reg := prometheus.NewRegistry() + reg.MustRegister(collector) + + metricCount := testutil.CollectAndCount(collector) + + expectedCount := 3 + if metricCount != expectedCount { + t.Errorf("expected %d metrics, got %d", expectedCount, metricCount) + } + + expectedMetrics := ` + # HELP icinga2_influxdb2writer_data_buffer_items InfluxDB2Writer icinga2_influxdb2writer_data_buffer_items + # TYPE icinga2_influxdb2writer_data_buffer_items gauge + icinga2_influxdb2writer_data_buffer_items{writer="influxdb2"} 74 + ` + + if err := testutil.CollectAndCompare(collector, strings.NewReader(expectedMetrics), "icinga2_influxdb2writer_data_buffer_items"); err != nil { + t.Errorf("unexpected metric difference:\n%s", err) + } +} diff --git a/internal/collector/influxdb_test.go b/internal/collector/influxdb_test.go new file mode 100644 index 0000000..615e5c9 --- /dev/null +++ b/internal/collector/influxdb_test.go @@ -0,0 +1,48 @@ +package collector + +import ( + "encoding/json" + "io" + "log/slog" + "os" + "strings" + "testing" + + "github.com/NETWAYS/icinga2-exporter/internal/icinga" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/testutil" +) + +func TestIcinga2InfluxDBWriterCollector_Collect(t *testing.T) { + client := &MockIcingaClient{} + + var result icinga.PerfdataResult + data, _ := os.ReadFile("testdata/influx.json") + json.Unmarshal(data, &result) + + client.SetPerfdataMetrics(result) + + logger := slog.New(slog.NewTextHandler(io.Discard, nil)) + collector := NewIcinga2InfluxDBCollector(client, logger) + + reg := prometheus.NewRegistry() + reg.MustRegister(collector) + + metricCount := testutil.CollectAndCount(collector) + + expectedCount := 3 + if metricCount != expectedCount { + t.Errorf("expected %d metrics, got %d", expectedCount, metricCount) + } + + expectedMetrics := ` + # HELP icinga2_influxdbwriter_data_buffer_items InfluxDBWriter icinga2_influxdbwriter_data_buffer_items + # TYPE icinga2_influxdbwriter_data_buffer_items gauge + icinga2_influxdbwriter_data_buffer_items{writer="influxdb"} 74 + ` + + if err := testutil.CollectAndCompare(collector, strings.NewReader(expectedMetrics), "icinga2_influxdbwriter_data_buffer_items"); err != nil { + t.Errorf("unexpected metric difference:\n%s", err) + } +} diff --git a/internal/collector/otlpmetrics.go b/internal/collector/otlpmetrics.go index 92a8b31..ec69ed8 100644 --- a/internal/collector/otlpmetrics.go +++ b/internal/collector/otlpmetrics.go @@ -1,6 +1,7 @@ package collector import ( + "fmt" "log/slog" "github.com/NETWAYS/icinga2-exporter/internal/icinga" @@ -9,33 +10,18 @@ import ( ) type Icinga2OTLPMetricsCollector struct { - icingaClient icinga.IcingaClient - logger *slog.Logger - otlpmetricswriter_otlp_metrics_work_queue_items *prometheus.Desc - otlpmetricswriter_otlp_metrics_work_queue_item_rate *prometheus.Desc - otlpmetricswriter_otlp_metrics_data_buffer_items *prometheus.Desc - otlpmetricswriter_otlp_metrics_data_buffer_bytes *prometheus.Desc + icingaClient icinga.IcingaClient + logger *slog.Logger } func NewIcinga2OTLPMetricsCollector(client icinga.IcingaClient, logger *slog.Logger) *Icinga2OTLPMetricsCollector { return &Icinga2OTLPMetricsCollector{ icingaClient: client, logger: logger, - otlpmetricswriter_otlp_metrics_work_queue_items: prometheus.NewDesc("icinga2_otlpmetricswriter_otlp_metrics_work_queue_items", "OTLPMetricsWriter work queue items", nil, nil), - otlpmetricswriter_otlp_metrics_work_queue_item_rate: prometheus.NewDesc("icinga2_otlpmetricswriter_otlp_metrics_work_queue_item_rate", "OTLPMetricsWriter work queue item rate", nil, nil), - otlpmetricswriter_otlp_metrics_data_buffer_items: prometheus.NewDesc("icinga2_otlpmetricswriter_otlp_metrics_data_buffer_items", "OTLPMetricsWriter data buffer items", nil, nil), - otlpmetricswriter_otlp_metrics_data_buffer_bytes: prometheus.NewDesc("icinga2_otlpmetricswriter_otlp_metrics_data_buffer_bytes", "OTLPMetricsWriter data buffer bytes", nil, nil), } } func (collector *Icinga2OTLPMetricsCollector) Describe(ch chan<- *prometheus.Desc) { - ch <- collector.otlpmetricswriter_otlp_metrics_work_queue_items - - ch <- collector.otlpmetricswriter_otlp_metrics_work_queue_item_rate - - ch <- collector.otlpmetricswriter_otlp_metrics_data_buffer_items - - ch <- collector.otlpmetricswriter_otlp_metrics_data_buffer_bytes } func (collector *Icinga2OTLPMetricsCollector) Collect(ch chan<- prometheus.Metric) { @@ -46,21 +32,29 @@ func (collector *Icinga2OTLPMetricsCollector) Collect(ch chan<- prometheus.Metri return } - for _, datapoint := range perfdata { - if datapoint.Label == "otlpmetricswriter_otlp_metrics_work_queue_items" { - ch <- prometheus.MustNewConstMetric(collector.otlpmetricswriter_otlp_metrics_work_queue_items, prometheus.GaugeValue, datapoint.Value) - } + for _, result := range perfdata.Results { + for component, writers := range result.Status { + for writer, metrics := range writers { + for metricName, value := range metrics { + if !isPerfdataMetric(metricName) { + continue + } - if datapoint.Label == "otlpmetricswriter_otlp_metrics_work_queue_item_rate" { - ch <- prometheus.MustNewConstMetric(collector.otlpmetricswriter_otlp_metrics_work_queue_item_rate, prometheus.GaugeValue, datapoint.Value) - } + safeMetricName := ensureValidMetricName(metricName) - if datapoint.Label == "otlpmetricswriter_otlp_metrics_data_buffer_items" { - ch <- prometheus.MustNewConstMetric(collector.otlpmetricswriter_otlp_metrics_data_buffer_items, prometheus.GaugeValue, datapoint.Value) - } + name := fmt.Sprintf("icinga2_%s_%s", component, safeMetricName) + + description := prometheus.NewDesc(name, "OTLPMetricsWriter "+name, []string{"writer"}, nil) + metric, err := prometheus.NewConstMetric(description, prometheus.GaugeValue, value, writer) + + if err != nil { + collector.logger.Error("Error creating metric "+metricName, "error", err.Error()) + continue + } - if datapoint.Label == "otlpmetricswriter_otlp_metrics_data_buffer_bytes" { - ch <- prometheus.MustNewConstMetric(collector.otlpmetricswriter_otlp_metrics_data_buffer_bytes, prometheus.GaugeValue, datapoint.Value) + ch <- metric + } + } } } } diff --git a/internal/collector/otlpmetrics_test.go b/internal/collector/otlpmetrics_test.go new file mode 100644 index 0000000..23888d2 --- /dev/null +++ b/internal/collector/otlpmetrics_test.go @@ -0,0 +1,49 @@ +package collector + +import ( + "encoding/json" + "io" + "log/slog" + "os" + "strings" + "testing" + + "github.com/NETWAYS/icinga2-exporter/internal/icinga" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/testutil" +) + +func TestIcinga2OTLPMetricsWriterCollector_Collect(t *testing.T) { + client := &MockIcingaClient{} + + var result icinga.PerfdataResult + data, _ := os.ReadFile("testdata/otlp.json") + json.Unmarshal(data, &result) + + client.SetPerfdataMetrics(result) + + logger := slog.New(slog.NewTextHandler(io.Discard, nil)) + collector := NewIcinga2OTLPMetricsCollector(client, logger) + + reg := prometheus.NewRegistry() + reg.MustRegister(collector) + + metricCount := testutil.CollectAndCount(collector) + + expectedCount := 8 + if metricCount != expectedCount { + t.Errorf("expected %d metrics, got %d", expectedCount, metricCount) + } + + expectedMetrics := ` + # HELP icinga2_otlpmetricswriter_work_queue_items OTLPMetricsWriter icinga2_otlpmetricswriter_work_queue_items + # TYPE icinga2_otlpmetricswriter_work_queue_items gauge + icinga2_otlpmetricswriter_work_queue_items{writer="otlp-metrics"} 0 + icinga2_otlpmetricswriter_work_queue_items{writer="otlp-elastic"} 0 + ` + + if err := testutil.CollectAndCompare(collector, strings.NewReader(expectedMetrics), "icinga2_otlpmetricswriter_work_queue_items"); err != nil { + t.Errorf("unexpected metric difference:\n%s", err) + } +} diff --git a/internal/collector/testdata/api.json b/internal/collector/testdata/api.json new file mode 100644 index 0000000..9d2b245 --- /dev/null +++ b/internal/collector/testdata/api.json @@ -0,0 +1,51 @@ +{ + "results": [ + { + "name": "ApiListener", + "perfdata": [ + { + "counter": false, + "crit": null, + "label": "api_num_conn_endpoints", + "max": null, + "min": null, + "type": "PerfdataValue", + "unit": "", + "value": 11, + "warn": null + } + ], + "status": { + "api": { + "conn_endpoints": [], + "http": { + "clients": 1 + }, + "identity": "unittest", + "json_rpc": { + "anonymous_clients": 1, + "relay_queue_item_rate": 0.999, + "relay_queue_items": 2, + "sync_queue_item_rate": 3, + "sync_queue_items": 4, + "work_queue_item_rate": 5 + }, + "not_conn_endpoints": [], + "num_conn_endpoints": 6, + "num_endpoints": 7, + "num_not_conn_endpoints": 8, + "zones": { + "unittest": { + "client_log_lag": 0, + "connected": true, + "endpoints": [ + "unittest" + ], + "parent_zone": "" + } + } + } + } + } + ] +} diff --git a/internal/collector/testdata/checker.json b/internal/collector/testdata/checker.json new file mode 100644 index 0000000..800ee3d --- /dev/null +++ b/internal/collector/testdata/checker.json @@ -0,0 +1,39 @@ +{ + "results": [ + { + "name": "CheckerComponent", + "perfdata": [ + { + "counter": false, + "crit": null, + "label": "checkercomponent_checker_idle", + "max": null, + "min": null, + "type": "PerfdataValue", + "unit": "", + "value": 14, + "warn": null + }, + { + "counter": false, + "crit": null, + "label": "checkercomponent_checker_pending", + "max": null, + "min": null, + "type": "PerfdataValue", + "unit": "", + "value": 41, + "warn": null + } + ], + "status": { + "checkercomponent": { + "checker": { + "idle": 14, + "pending": 41 + } + } + } + } + ] +} diff --git a/internal/collector/testdata/graphite.json b/internal/collector/testdata/graphite.json new file mode 100644 index 0000000..9bfb6f9 --- /dev/null +++ b/internal/collector/testdata/graphite.json @@ -0,0 +1,40 @@ +{ + "results": [ + { + "name": "GraphiteWriter", + "perfdata": [ + { + "counter": false, + "crit": null, + "label": "graphitewriter_graphite_work_queue_items", + "max": null, + "min": null, + "type": "PerfdataValue", + "unit": "", + "value": 2, + "warn": null + }, + { + "counter": false, + "crit": null, + "label": "graphitewriter_graphite_work_queue_item_rate", + "max": null, + "min": null, + "type": "PerfdataValue", + "unit": "", + "value": 0.216, + "warn": null + } + ], + "status": { + "graphitewriter": { + "graphite": { + "connected": false, + "work_queue_item_rate": 0.216, + "work_queue_items": 2 + } + } + } + } + ] +} diff --git a/internal/collector/testdata/influx.json b/internal/collector/testdata/influx.json new file mode 100644 index 0000000..25a8221 --- /dev/null +++ b/internal/collector/testdata/influx.json @@ -0,0 +1,51 @@ +{ + "results": [ + { + "name": "InfluxdbWriter", + "perfdata": [ + { + "counter": false, + "crit": null, + "label": "influxdbwriter_influxdb_work_queue_items", + "max": null, + "min": null, + "type": "PerfdataValue", + "unit": "", + "value": 0, + "warn": null + }, + { + "counter": false, + "crit": null, + "label": "influxdbwriter_influxdb_work_queue_item_rate", + "max": null, + "min": null, + "type": "PerfdataValue", + "unit": "", + "value": 0.216, + "warn": null + }, + { + "counter": false, + "crit": null, + "label": "influxdbwriter_influxdb_data_queue_items", + "max": null, + "min": null, + "type": "PerfdataValue", + "unit": "", + "value": 74, + "warn": null + } + ], + "status": { + "influxdbwriter": { + "influxdb": { + "data_buffer_items": 74, + "work_queue_item_rate": 0.216, + "work_queue_items": 0 + } + } + } + } + ] +} diff --git a/internal/collector/testdata/influx2.json b/internal/collector/testdata/influx2.json new file mode 100644 index 0000000..70373ee --- /dev/null +++ b/internal/collector/testdata/influx2.json @@ -0,0 +1,51 @@ +{ + "results": [ + { + "name": "Influxdb2Writer", + "perfdata": [ + { + "counter": false, + "crit": null, + "label": "influxdb2writer_influxdb2_work_queue_items", + "max": null, + "min": null, + "type": "PerfdataValue", + "unit": "", + "value": 0, + "warn": null + }, + { + "counter": false, + "crit": null, + "label": "influxdb2writer_influxdb2_work_queue_item_rate", + "max": null, + "min": null, + "type": "PerfdataValue", + "unit": "", + "value": 0.21666666666666667, + "warn": null + }, + { + "counter": false, + "crit": null, + "label": "influxdb2writer_influxdb2_data_queue_items", + "max": null, + "min": null, + "type": "PerfdataValue", + "unit": "", + "value": 74, + "warn": null + } + ], + "status": { + "influxdb2writer": { + "influxdb2": { + "data_buffer_items": 74, + "work_queue_item_rate": 0.21666666666666667, + "work_queue_items": 0 + } + } + } + } + ] +} diff --git a/internal/collector/testdata/otlp.json b/internal/collector/testdata/otlp.json new file mode 100644 index 0000000..2afd2cf --- /dev/null +++ b/internal/collector/testdata/otlp.json @@ -0,0 +1,113 @@ +{ + "results": [ + { + "name": "OTLPMetricsWriter", + "perfdata": [ + { + "counter": false, + "crit": null, + "label": "otlpmetricswriter_otlp-elastic_work_queue_items", + "max": null, + "min": null, + "type": "PerfdataValue", + "unit": "", + "value": 0, + "warn": null + }, + { + "counter": false, + "crit": null, + "label": "otlpmetricswriter_otlp-elastic_work_queue_item_rate", + "max": null, + "min": null, + "type": "PerfdataValue", + "unit": "", + "value": 0.5833333333333334, + "warn": null + }, + { + "counter": false, + "crit": null, + "label": "otlpmetricswriter_otlp-elastic_data_buffer_items", + "max": null, + "min": null, + "type": "PerfdataValue", + "unit": "", + "value": 48095, + "warn": null + }, + { + "counter": false, + "crit": null, + "label": "otlpmetricswriter_otlp-elastic_data_buffer_bytes", + "max": null, + "min": null, + "type": "PerfdataValue", + "unit": "bytes", + "value": 4092873, + "warn": null + }, + { + "counter": false, + "crit": null, + "label": "otlpmetricswriter_otlp-metrics_work_queue_items", + "max": null, + "min": null, + "type": "PerfdataValue", + "unit": "", + "value": 0, + "warn": null + }, + { + "counter": false, + "crit": null, + "label": "otlpmetricswriter_otlp-metrics_work_queue_item_rate", + "max": null, + "min": null, + "type": "PerfdataValue", + "unit": "", + "value": 0.5833333333333334, + "warn": null + }, + { + "counter": false, + "crit": null, + "label": "otlpmetricswriter_otlp-metrics_data_buffer_items", + "max": null, + "min": null, + "type": "PerfdataValue", + "unit": "", + "value": 153, + "warn": null + }, + { + "counter": false, + "crit": null, + "label": "otlpmetricswriter_otlp-metrics_data_buffer_bytes", + "max": null, + "min": null, + "type": "PerfdataValue", + "unit": "bytes", + "value": 13784, + "warn": null + } + ], + "status": { + "otlpmetricswriter": { + "otlp-elastic": { + "data_buffer_bytes": 4092873, + "data_buffer_items": 48095, + "work_queue_item_rate": 0.5833333333333334, + "work_queue_items": 0 + }, + "otlp-metrics": { + "data_buffer_bytes": 13784, + "data_buffer_items": 153, + "work_queue_item_rate": 0.5833333333333334, + "work_queue_items": 0 + } + } + } + } + ] +} diff --git a/internal/collector/testing.go b/internal/collector/testing.go index 6993fda..30c97e2 100644 --- a/internal/collector/testing.go +++ b/internal/collector/testing.go @@ -8,15 +8,17 @@ import ( type MockIcingaClient struct { perfdata icinga.PerfdataResult cib icinga.CIBResult + api icinga.APIResult application icinga.ApplicationResult + checker icinga.CheckerComponentResult } func (m *MockIcingaClient) SetPerfdataMetrics(perfdata icinga.PerfdataResult) { m.perfdata = perfdata } -func (m *MockIcingaClient) GetPerfdataMetrics(endpoint string) ([]icinga.Perfdata, error) { - return m.perfdata.Results[0].Perfdata, nil +func (m *MockIcingaClient) GetPerfdataMetrics(endpoint string) (icinga.PerfdataResult, error) { + return m.perfdata, nil } func (m *MockIcingaClient) SetCIBMetrics(cib icinga.CIBResult) { @@ -27,6 +29,22 @@ func (m *MockIcingaClient) GetCIBMetrics() (icinga.CIBResult, error) { return m.cib, nil } +func (m *MockIcingaClient) SetAPIMetrics(api icinga.APIResult) { + m.api = api +} + +func (m *MockIcingaClient) GetAPIMetrics() (icinga.APIResult, error) { + return m.api, nil +} + +func (m *MockIcingaClient) SetCheckerComponentMetrics(checker icinga.CheckerComponentResult) { + m.checker = checker +} + +func (m *MockIcingaClient) GetCheckerComponentMetrics() (icinga.CheckerComponentResult, error) { + return m.checker, nil +} + func (m *MockIcingaClient) SetApplicationMetrics(application icinga.ApplicationResult) { m.application = application } diff --git a/internal/collector/util.go b/internal/collector/util.go new file mode 100644 index 0000000..02f4cdb --- /dev/null +++ b/internal/collector/util.go @@ -0,0 +1,24 @@ +package collector + +import ( + "regexp" + "strings" +) + +var invalidChars = regexp.MustCompile(`[^a-zA-Z0-9_:]`) + +func ensureValidMetricName(s string) string { + return invalidChars.ReplaceAllString(s, "_") +} + +func isPerfdataMetric(s string) bool { + if strings.HasPrefix(s, "data") { + return true + } + + if strings.HasPrefix(s, "work") { + return true + } + + return false +} diff --git a/internal/icinga/client.go b/internal/icinga/client.go index b7f2937..1269929 100644 --- a/internal/icinga/client.go +++ b/internal/icinga/client.go @@ -55,9 +55,11 @@ type Client struct { // IcingaClient is an interface that we use to simplify testing type IcingaClient interface { - GetPerfdataMetrics(endpoint string) ([]Perfdata, error) + GetPerfdataMetrics(endpoint string) (PerfdataResult, error) GetCIBMetrics() (CIBResult, error) GetApplicationMetrics() (ApplicationResult, error) + GetAPIMetrics() (APIResult, error) + GetCheckerComponentMetrics() (CheckerComponentResult, error) } // NewClient returns a configured client @@ -107,28 +109,26 @@ func NewClient(c Config) (*Client, error) { } // GetPerfdataMetrics returns the perfdata from a given status API endpoint -func (c *Client) GetPerfdataMetrics(endpoint string) ([]Perfdata, error) { +func (c *Client) GetPerfdataMetrics(endpoint string) (PerfdataResult, error) { var result PerfdataResult body, errBody := c.fetchJSON(endpoint) if errBody != nil { - return nil, fmt.Errorf("error fetching response: %w", errBody) + return result, fmt.Errorf("error fetching response: %w", errBody) } errDecode := json.Unmarshal(body, &result) if errDecode != nil { - return nil, fmt.Errorf("error parsing response: %w", errDecode) + return result, fmt.Errorf("error parsing response: %w", errDecode) } if len(result.Results) < 1 { - return nil, fmt.Errorf("no results for '%s' endpoint", endpoint) + return result, fmt.Errorf("no results for '%s' endpoint", endpoint) } - r := result.Results[0] - - return r.Perfdata, nil + return result, nil } func (c *Client) GetCIBMetrics() (CIBResult, error) { @@ -167,6 +167,42 @@ func (c *Client) GetApplicationMetrics() (ApplicationResult, error) { return result, nil } +func (c *Client) GetAPIMetrics() (APIResult, error) { + var result APIResult + + body, errBody := c.fetchJSON(EndpointApiListener) + + if errBody != nil { + return result, fmt.Errorf("error fetching response: %w", errBody) + } + + errDecode := json.Unmarshal(body, &result) + + if errDecode != nil { + return result, fmt.Errorf("error parsing response: %w", errDecode) + } + + return result, nil +} + +func (c *Client) GetCheckerComponentMetrics() (CheckerComponentResult, error) { + var result CheckerComponentResult + + body, errBody := c.fetchJSON(EndpointCheckerComponent) + + if errBody != nil { + return result, fmt.Errorf("error fetching response: %w", errBody) + } + + errDecode := json.Unmarshal(body, &result) + + if errDecode != nil { + return result, fmt.Errorf("error parsing response: %w", errDecode) + } + + return result, nil +} + func (c *Client) fetchJSON(endpoint string) ([]byte, error) { // Lookup data in the cache we go out and bother the Icinga API if elem, ok := c.cache.Get(endpoint); ok { diff --git a/internal/icinga/client_test.go b/internal/icinga/client_test.go index eac4cf6..2a2c0b1 100644 --- a/internal/icinga/client_test.go +++ b/internal/icinga/client_test.go @@ -11,10 +11,11 @@ import ( ) const ( - icingaTestDataCIB1 = "testdata/cib1.json" - icingaTestDataAPP1 = "testdata/app1.json" - icingaTestDataAPI1 = "testdata/api1.json" - icingaTestDataCheck1 = "testdata/checker1.json" + icingaTestDataCIB1 = "testdata/cib1.json" + icingaTestDataAPP1 = "testdata/app1.json" + icingaTestDataAPI1 = "testdata/api1.json" + // icingaTestDataCheck1 = "testdata/checker1.json" + icingaTestDataPerf1 = "testdata/otlp.json" ) func loadTestdata(filepath string) []byte { @@ -142,37 +143,47 @@ func Test_GetApplicationMetrics(t *testing.T) { func Test_GetPerfdataMetrics(t *testing.T) { testcases := map[string]struct { - expected []Perfdata + expected PerfdataResult endpoint string server *httptest.Server }{ - "api": { + "otlp": { server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) - w.Write(loadTestdata(icingaTestDataAPI1)) + w.Write(loadTestdata(icingaTestDataPerf1)) })), - endpoint: EndpointApiListener, - expected: []Perfdata{ - { - Label: "api_num_conn_endpoints", - Value: 11, - }, - }, - }, - "checker": { - server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.WriteHeader(http.StatusOK) - w.Write(loadTestdata(icingaTestDataCheck1)) - })), - endpoint: EndpointCheckerComponent, - expected: []Perfdata{ - { - Label: "checkercomponent_checker_idle", - Value: 15, - }, - { - Label: "checkercomponent_checker_pending", - Value: 10, + endpoint: EndpointOTLPMetricsWriter, + expected: PerfdataResult{ + Results: []struct { + Name string `json:"name"` + Perfdata []Perfdata `json:"perfdata,omitempty"` + Status map[string]map[string]map[string]float64 `json:"status"` + }{ + { + Name: "OTLPMetricsWriter", + Perfdata: []Perfdata{ + { + IsCounter: false, + Label: "otlpmetricswriter_otlp-elastic_work_queue_items", + Value: 0, + }, + { + IsCounter: false, + Label: "otlpmetricswriter_otlp-elastic_work_queue_item_rate", + Value: 0.5833333333333334, + }, + }, + Status: map[string]map[string]map[string]float64{ + "otlpmetricswriter": { + "otlp-elastic": { + "data_buffer_items": 48095, + }, + "otlp-metrics": { + "data_buffer_items": 153, + }, + }, + }, + }, }, }, }, diff --git a/internal/icinga/model.go b/internal/icinga/model.go index 6b7fbaa..62cd700 100644 --- a/internal/icinga/model.go +++ b/internal/icinga/model.go @@ -8,8 +8,9 @@ type Perfdata struct { type PerfdataResult struct { Results []struct { - Name string `json:"name"` - Perfdata []Perfdata `json:"perfdata,omitempty"` + Name string `json:"name"` + Perfdata []Perfdata `json:"perfdata,omitempty"` + Status map[string]map[string]map[string]float64 `json:"status"` } `json:"results"` } @@ -45,7 +46,44 @@ type App struct { type CheckerComponentResult struct { Results []struct { - Name string `json:"name"` - Perfdata []Perfdata `json:"perfdata,omitempty"` + Name string `json:"name"` + Status struct { + CheckerComponent CheckerComponent `json:"checkercomponent"` + } `json:"status"` + } `json:"results"` +} + +type CheckerComponent struct { + Checker struct { + Idle float64 `json:"idle"` + Pending float64 `json:"pending"` + } `json:"checker"` +} + +type APIResult struct { + Results []struct { + Name string `json:"name"` + Status struct { + API API `json:"api"` + } `json:"status"` } `json:"results"` } + +type API struct { + NumConnEndpoints float64 `json:"num_conn_endpoints"` + NumNotConnEndpoints float64 `json:"num_not_conn_endpoints"` + NumEndpoints float64 `json:"num_endpoints"` + HTTP struct { + Clients float64 `json:"clients"` + } `json:"http"` + JSONRPC JSONRPC `json:"json_rpc"` +} + +type JSONRPC struct { + AnonymousClients float64 `json:"anonymous_clients"` + RelayQueueItemRate float64 `json:"relay_queue_item_rate"` + RelayQueueItems float64 `json:"relay_queue_items"` + SyncQueueItemRate float64 `json:"sync_queue_item_rate"` + SyncQueueItems float64 `json:"sync_queue_items"` + WorkQueueItemRate float64 `json:"work_queue_item_rate"` +} diff --git a/internal/icinga/testdata/otlp.json b/internal/icinga/testdata/otlp.json new file mode 100644 index 0000000..fb00772 --- /dev/null +++ b/internal/icinga/testdata/otlp.json @@ -0,0 +1,41 @@ +{ + "results": [ + { + "name": "OTLPMetricsWriter", + "perfdata": [ + { + "counter": false, + "crit": null, + "label": "otlpmetricswriter_otlp-elastic_work_queue_items", + "max": null, + "min": null, + "type": "PerfdataValue", + "unit": "", + "value": 0, + "warn": null + }, + { + "counter": false, + "crit": null, + "label": "otlpmetricswriter_otlp-elastic_work_queue_item_rate", + "max": null, + "min": null, + "type": "PerfdataValue", + "unit": "", + "value": 0.5833333333333334, + "warn": null + } + ], + "status": { + "otlpmetricswriter": { + "otlp-elastic": { + "data_buffer_items": 48095 + }, + "otlp-metrics": { + "data_buffer_items": 153 + } + } + } + } + ] +}