Skip to content
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
1 change: 1 addition & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ linters:
- wsl_v5
- gomodguard_v2
disable:
- goconst
- wsl
- gomodguard
- dupl
Expand Down
84 changes: 59 additions & 25 deletions internal/collector/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -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),
}
}

Expand All @@ -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)
}
48 changes: 48 additions & 0 deletions internal/collector/api_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
19 changes: 10 additions & 9 deletions internal/collector/checker.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
48 changes: 48 additions & 0 deletions internal/collector/checker_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
52 changes: 27 additions & 25 deletions internal/collector/graphite.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package collector

import (
"fmt"
"log/slog"

"github.com/NETWAYS/icinga2-exporter/internal/icinga"
Expand All @@ -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
}
}
}
}
}
48 changes: 48 additions & 0 deletions internal/collector/graphite_test.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
Loading