From 1f0fb40cafb71e0517e4265c7ae05d4566149f82 Mon Sep 17 00:00:00 2001 From: ihatemodels Date: Fri, 29 Mar 2024 15:12:59 +0000 Subject: [PATCH] FEATURE: implement tcp stats metrics --- .github/pull_request_template.md | 31 +++++++++++++++ internal/collector/protocol.go | 68 ++++++++++++++++++++++++++++++-- internal/options/ops.go | 8 ++-- opnsense/protocol_statistics.go | 32 +++++++++++++-- opnsense/unbound_dns.go | 4 +- 5 files changed, 128 insertions(+), 15 deletions(-) create mode 100644 .github/pull_request_template.md diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..22f6dbe --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,31 @@ +# Pull Request Template + +## Description + +Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change. + +Fixes # (issue) + +## Type of change + +Please delete options that are not relevant. + +- [ ] Bug fix (non-breaking change which fixes an issue) +- [ ] New feature (non-breaking change which adds functionality) +- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) +- [ ] This change requires a documentation update + +## How Has This Been Tested? + +Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration + +- [ ] Test A +- [ ] Test B + +## Checklist: + +- [ ] I have commented my code, particularly in hard-to-understand areas +- [ ] I have made corresponding changes to the documentation +- [ ] I have added tests that prove my fix is effective or that my feature works +- [ ] New and existing unit tests pass locally with my changes +- [ ] Any dependent changes have been merged and published in downstream modules \ No newline at end of file diff --git a/internal/collector/protocol.go b/internal/collector/protocol.go index 128f90f..e198aad 100644 --- a/internal/collector/protocol.go +++ b/internal/collector/protocol.go @@ -8,9 +8,14 @@ import ( ) type protocolCollector struct { - log log.Logger - subsystem string - instance string + log log.Logger + subsystem string + instance string + tcpConnectionCountByState *prometheus.Desc + tcpSentPackets *prometheus.Desc + tcpReceivedPackets *prometheus.Desc + arpSentRequests *prometheus.Desc + arpReceivedRequests *prometheus.Desc } func init() { @@ -28,13 +33,68 @@ func (c *protocolCollector) Register(namespace, instanceLabel string, log log.Lo c.instance = instanceLabel level.Debug(c.log). Log("msg", "Registering collector", "collector", c.Name()) + + c.tcpConnectionCountByState = buildPrometheusDesc(c.subsystem, "tcp_connection_count_by_state", + "Number of TCP connections by state", + []string{"state"}, + ) + + c.tcpSentPackets = buildPrometheusDesc(c.subsystem, "tcp_sent_packets_total", + "Number of sent TCP packets ", + nil, + ) + + c.tcpReceivedPackets = buildPrometheusDesc(c.subsystem, "tcp_received_packets_total", + "Number of received TCP packets", + nil, + ) + + c.arpSentRequests = buildPrometheusDesc(c.subsystem, "arp_sent_requests_total", + "Number of sent ARP requests", + nil, + ) + + c.arpReceivedRequests = buildPrometheusDesc(c.subsystem, "arp_received_requests_total", + "Number of received ARP requests", + nil, + ) } func (c *protocolCollector) Describe(ch chan<- *prometheus.Desc) { - + ch <- c.tcpConnectionCountByState + ch <- c.tcpSentPackets + ch <- c.tcpReceivedPackets + ch <- c.arpSentRequests + ch <- c.arpReceivedRequests } func (c *protocolCollector) Update(client *opnsense.Client, ch chan<- prometheus.Metric) *opnsense.APICallError { + data, err := client.FetchProtocolStatistics() + if err != nil { + return err + } + + for state, count := range data.TCPConnectionCountByState { + ch <- prometheus.MustNewConstMetric( + c.tcpConnectionCountByState, prometheus.GaugeValue, float64(count), state, c.instance, + ) + } + + ch <- prometheus.MustNewConstMetric( + c.tcpSentPackets, prometheus.CounterValue, float64(data.TCPSentPackets), c.instance, + ) + + ch <- prometheus.MustNewConstMetric( + c.tcpReceivedPackets, prometheus.CounterValue, float64(data.TCPReceivedPackets), c.instance, + ) + + ch <- prometheus.MustNewConstMetric( + c.arpSentRequests, prometheus.CounterValue, float64(data.ARPSentRequests), c.instance, + ) + + ch <- prometheus.MustNewConstMetric( + c.arpReceivedRequests, prometheus.CounterValue, float64(data.ARPReceivedRequests), c.instance, + ) return nil } diff --git a/internal/options/ops.go b/internal/options/ops.go index 4c3d4de..83ac462 100644 --- a/internal/options/ops.go +++ b/internal/options/ops.go @@ -88,6 +88,7 @@ func opsAPIKey() (string, error) { return *opnsenseAPIKey, nil } +// OPNSenseConfig holds the configuration for the OPNsense API. type OPNSenseConfig struct { Protocol string Host string @@ -96,11 +97,8 @@ type OPNSenseConfig struct { Insecure bool } -func (c *OPNSenseConfig) String() string { - return fmt.Sprintf("Protocol: %s, Host: %s, APIKey: %s, APISecret: %s, Insecure: %t", - c.Protocol, c.Host, c.APIKey, c.APISecret, c.Insecure) -} - +// Validate checks if the configuration is valid. +// returns an error on any missing value func (c *OPNSenseConfig) Validate() error { if c.Protocol != "http" && c.Protocol != "https" { return fmt.Errorf("protocol must be one of: [http, https]") diff --git a/opnsense/protocol_statistics.go b/opnsense/protocol_statistics.go index bc04d71..dea8dd9 100644 --- a/opnsense/protocol_statistics.go +++ b/opnsense/protocol_statistics.go @@ -258,24 +258,48 @@ type protocolStatisticsResponse struct { } type ProtocolStatistics struct { + TCPSentPackets int + TCPReceivedPackets int + ARPSentRequests int + ARPReceivedRequests int TCPConnectionCountByState map[string]int } func (c *Client) FetchProtocolStatistics() (ProtocolStatistics, *APICallError) { var ( resp protocolStatisticsResponse - data ProtocolStatistics ) url, ok := c.endpoints["protocolStatistics"] if !ok { - return data, &APICallError{ + return ProtocolStatistics{}, &APICallError{ Endpoint: "protocolStatistics", StatusCode: 404, Message: "endpoint not found in client endpoints", } } if err := c.do("GET", url, nil, &resp); err != nil { - return data, err + return ProtocolStatistics{}, err } - return data, nil + + out := ProtocolStatistics{ + TCPSentPackets: resp.Statistics.TCP.SentPackets, + TCPReceivedPackets: resp.Statistics.TCP.ReceivedPackets, + ARPSentRequests: resp.Statistics.Arp.SentRequests, + ARPReceivedRequests: resp.Statistics.Arp.ReceivedRequests, + TCPConnectionCountByState: map[string]int{ + "CLOSED": resp.Statistics.TCP.TCPConnectionCountByState.Closed, + "LISTEN": resp.Statistics.TCP.TCPConnectionCountByState.Listen, + "SYN_SENT": resp.Statistics.TCP.TCPConnectionCountByState.SynSent, + "SYN_RCVD": resp.Statistics.TCP.TCPConnectionCountByState.SynRcvd, + "ESTABLISHED": resp.Statistics.TCP.TCPConnectionCountByState.Established, + "CLOSE_WAIT": resp.Statistics.TCP.TCPConnectionCountByState.CloseWait, + "FIN_WAIT_1": resp.Statistics.TCP.TCPConnectionCountByState.FinWait1, + "CLOSING": resp.Statistics.TCP.TCPConnectionCountByState.Closing, + "LAST_ACK": resp.Statistics.TCP.TCPConnectionCountByState.LastAck, + "FIN_WAIT_2": resp.Statistics.TCP.TCPConnectionCountByState.FinWait2, + "TIME_WAIT": resp.Statistics.TCP.TCPConnectionCountByState.TimeWait, + }, + } + + return out, nil } diff --git a/opnsense/unbound_dns.go b/opnsense/unbound_dns.go index 0bfc6ae..9b7bf57 100644 --- a/opnsense/unbound_dns.go +++ b/opnsense/unbound_dns.go @@ -233,11 +233,11 @@ func (c *Client) FetchUnboundOverview() (UnboundDNSOverview, *APICallError) { } } data.AnnswerBogusTotal, errConvertion = parseStringToInt(response.Data.Num.Answer.Bogus, url) - if err != nil { + if errConvertion != nil { return data, errConvertion } data.AnswerSecureTotal, errConvertion = parseStringToInt(response.Data.Num.Answer.Secure, url) - if err != nil { + if errConvertion != nil { return data, errConvertion } return data, nil