diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index f6feb75..afb6c3d 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -28,5 +28,5 @@ jobs: - name: Build run: go build -v . -# - name: Test -# run: go test -v . + - name: Test + run: go test -v ./... diff --git a/client/client_names.go b/client/client_names.go index 1429329..7d095ed 100644 --- a/client/client_names.go +++ b/client/client_names.go @@ -33,7 +33,7 @@ func (client *FTLClient) GetClientNames() (*[]Client, error) { var clients []Client for { name, err := readString(conn) - if err == EOF { + if err == errEndOfInput { break } if err != nil { diff --git a/client/clients.go b/client/clients.go index 3527439..1ef4610 100644 --- a/client/clients.go +++ b/client/clients.go @@ -14,23 +14,22 @@ package client import ( - "encoding/binary" "net" ) // GetTopClients retrieves the list of clients together with amount of queries // made by each client from response of `>top-clients` command -func (client *FTLClient) GetTopClients() (*Entries, error) { +func (client *FTLClient) GetTopClients() (*TopEntries, error) { return topClientsFor(">top-clients", client) } // GetTopBlockedClients retrieves the list of clients together with amount of blocked // queries made by each client from response of `>top-clients` command -func (client *FTLClient) GetTopBlockedClients() (*Entries, error) { +func (client *FTLClient) GetTopBlockedClients() (*TopEntries, error) { return topClientsFor(">top-clients blocked", client) } -func topClientsFor(command string, client *FTLClient) (*Entries, error) { +func topClientsFor(command string, client *FTLClient) (*TopEntries, error) { conn, err := net.DialUnix("unix", nil, client.addr) if err != nil { return nil, err @@ -41,15 +40,18 @@ func topClientsFor(command string, client *FTLClient) (*Entries, error) { return nil, err } - var result Entries - - if err := binary.Read(conn, binary.BigEndian, &result.Total); err != nil { + total, err := readInt32(conn) + if err != nil { return nil, err } + result := TopEntries{ + Total: total, + } + for { _, err := readString(conn) - if err == EOF { + if err == errEndOfInput { break } if err != nil { @@ -61,15 +63,12 @@ func topClientsFor(command string, client *FTLClient) (*Entries, error) { return nil, err } - count, err := readUint32(conn) + count, err := readInt32(conn) if err != nil { return nil, err } - result.List = append(result.List, struct { - Entry string - Count uint32 - }{Entry: address, Count: count}) + result.Entries = append(result.Entries, entry{Label: address, Count: count}) } return &result, nil diff --git a/client/clients_over_time.go b/client/clients_over_time.go index 4930ca7..75003c9 100644 --- a/client/clients_over_time.go +++ b/client/clients_over_time.go @@ -14,12 +14,10 @@ package client import ( - "encoding/binary" - "io" "net" ) -// GetQueriesOverTime retrieves amount of queries grouped by client +// GetClientsOverTime retrieves amount of queries grouped by client // for the last 24 hours aggregated over 10 minute intervals // from response of `>ClientsoverTime` command // Warning: API might be not public @@ -36,29 +34,23 @@ func (client *FTLClient) GetClientsOverTime() (*[]TimestampClients, error) { var timestamps []TimestampClients for { - var format uint8 - err := binary.Read(conn, binary.BigEndian, &format) - - if err == io.EOF || format == formatEOF { + timestamp, err := readInt32(conn) + if err == errEndOfInput { break } - - var clients []Int32Block - - var timestamp uint32 - err = binary.Read(conn, binary.BigEndian, ×tamp) if err != nil { return nil, err } + var clients []int + for { - var clientQueryCount Int32Block - err := binary.Read(conn, binary.BigEndian, &clientQueryCount) + clientQueryCount, err := readInt32(conn) if err != nil { return nil, err } - if clientQueryCount.Value == -1 { + if clientQueryCount == -1 { break } diff --git a/client/db_stats.go b/client/db_stats.go index 353fd62..df6a266 100644 --- a/client/db_stats.go +++ b/client/db_stats.go @@ -14,7 +14,6 @@ package client import ( - "encoding/binary" "net" ) @@ -30,10 +29,18 @@ func (client *FTLClient) GetDBStats() (*DBStats, error) { return nil, err } - var stats DBStats - if err := binary.Read(conn, binary.BigEndian, &stats); err != nil { + rows, err := readInt32(conn) + if err != nil { + return nil, err + } + + size, err := readInt64(conn) + if err != nil { return nil, err } - return &stats, nil + return &DBStats{ + RowsCount: rows, + FileSize: size, + }, nil } diff --git a/client/domains.go b/client/domains.go index 163dfde..fbf4c10 100644 --- a/client/domains.go +++ b/client/domains.go @@ -14,23 +14,22 @@ package client import ( - "encoding/binary" "net" ) // GetTopDomains retrieves the list of domains together with amount of queries // made for each domain from response of `>top-domains` command -func (client *FTLClient) GetTopDomains() (*Entries, error) { +func (client *FTLClient) GetTopDomains() (*TopEntries, error) { return topQueriesFor(">top-domains", client) } // GetTopAds retrieves the list of ad domains together with amount of queries // made for each domain from response of `>top-ads` command -func (client *FTLClient) GetTopAds() (*Entries, error) { +func (client *FTLClient) GetTopAds() (*TopEntries, error) { return topQueriesFor(">top-ads", client) } -func topQueriesFor(command string, client *FTLClient) (*Entries, error) { +func topQueriesFor(command string, client *FTLClient) (*TopEntries, error) { conn, err := net.DialUnix("unix", nil, client.addr) if err != nil { return nil, err @@ -41,29 +40,30 @@ func topQueriesFor(command string, client *FTLClient) (*Entries, error) { return nil, err } - var result Entries - if err := binary.Read(conn, binary.BigEndian, &result.Total); err != nil { + total, err := readInt32(conn) + if err != nil { return nil, err } + result := TopEntries{ + Total: int(total), + } + for { domainName, err := readString(conn) - if err == EOF { + if err == errEndOfInput { break } if err != nil { return nil, err } - domainCount, err := readUint32(conn) + domainCount, err := readInt32(conn) if err != nil { return nil, err } - result.List = append(result.List, struct { - Entry string - Count uint32 - }{Entry: domainName, Count: domainCount}) + result.Entries = append(result.Entries, entry{Label: domainName, Count: domainCount}) } return &result, nil diff --git a/client/forward_destinations.go b/client/forward_destinations.go index 053fc1b..74769cc 100644 --- a/client/forward_destinations.go +++ b/client/forward_destinations.go @@ -33,7 +33,7 @@ func (client *FTLClient) GetForwardDestinations() (*[]UpstreamDestination, error var destinations []UpstreamDestination for { name, err := readString(conn) - if err == EOF { + if err == errEndOfInput { break } if err != nil { diff --git a/client/ftl_client.go b/client/ftl_client.go index d8b72f5..9cfd578 100644 --- a/client/ftl_client.go +++ b/client/ftl_client.go @@ -22,7 +22,8 @@ import ( ) const ( - formatUint32 uint8 = 0xd2 // 210 + formatInt32 uint8 = 0xd2 // 210 + formatInt64 uint8 = 0xd3 // 211 formatFloat32 uint8 = 0xca // 202 formatUInt8 uint8 = 0xcc // 204 formatString uint8 = 0xdb // 219 @@ -31,8 +32,8 @@ const ( formatEOF uint8 = 0xc1 // 193 ) -var EOF = errors.New("EOF") -var invalidFormat = errors.New("unexpected format") +var errEndOfInput = errors.New("end of the input") +var errInvalidFormat = errors.New("unexpected format") // FTLClient for Pi-holes's FTL daemon. Contains address to a unix socket type FTLClient struct { @@ -66,18 +67,18 @@ func readString(conn *net.UnixConn) (string, error) { var format uint8 if err := binary.Read(conn, binary.BigEndian, &format); err != nil { if err == io.EOF { - return "", EOF + return "", errEndOfInput } return "", err } if format == formatEOF { - return "", EOF + return "", errEndOfInput } if format != formatString { - return "", invalidFormat + return "", errInvalidFormat } var length uint32 @@ -98,18 +99,18 @@ func readFloat32(conn *net.UnixConn) (float32, error) { var format uint8 if err := binary.Read(conn, binary.BigEndian, &format); err != nil { if err == io.EOF { - return 0.0, EOF + return 0.0, errEndOfInput } return 0.0, err } if format == formatEOF { - return 0.0, EOF + return 0.0, errEndOfInput } if format != formatFloat32 { - return 0.0, invalidFormat + return 0.0, errInvalidFormat } var value float32 @@ -120,30 +121,82 @@ func readFloat32(conn *net.UnixConn) (float32, error) { return value, nil } -func readUint32(conn *net.UnixConn) (uint32, error) { +func readInt32(conn *net.UnixConn) (int, error) { var format uint8 if err := binary.Read(conn, binary.BigEndian, &format); err != nil { if err == io.EOF { - return 0, EOF + return 0, errEndOfInput } return 0, err } if format == formatEOF { - return 0, EOF + return 0, errEndOfInput } - if format != formatUint32 { - return 0, invalidFormat + if format != formatInt32 { + return 0, errInvalidFormat } - var value uint32 + var value int32 if err := binary.Read(conn, binary.BigEndian, &value); err != nil { return 0, err } - return value, nil + return int(value), nil +} + +func readInt64(conn *net.UnixConn) (int, error) { + var format uint8 + if err := binary.Read(conn, binary.BigEndian, &format); err != nil { + if err == io.EOF { + return 0, errEndOfInput + } + + return 0, err + } + + if format == formatEOF { + return 0, errEndOfInput + } + + if format != formatInt64 { + return 0, errInvalidFormat + } + + var value int64 + if err := binary.Read(conn, binary.BigEndian, &value); err != nil { + return 0, err + } + + return int(value), nil +} + +func readMapCount(conn *net.UnixConn) (int, error) { + var format uint8 + if err := binary.Read(conn, binary.BigEndian, &format); err != nil { + if err == io.EOF { + return 0, errEndOfInput + } + + return 0, err + } + + if format == formatEOF { + return 0, errEndOfInput + } + + if format != formatMap16 { + return 0, errInvalidFormat + } + + var value int16 + if err := binary.Read(conn, binary.BigEndian, &value); err != nil { + return 0, err + } + + return int(value), nil } func sendCommand(conn *net.UnixConn, command string) error { diff --git a/client/model.go b/client/model.go index 7559df0..cc463db 100644 --- a/client/model.go +++ b/client/model.go @@ -27,71 +27,71 @@ type Stats struct { Status int } -// DBStats represents the response of `>db-stats` command +// DBStats represents the response of `>db-stats` command. +// It contains amount of rows in database and current file size of the database type DBStats struct { - Rows UInt32Block - Size UInt64Block + RowsCount int + FileSize int } -type UInt32Block struct { - _ uint8 - Value uint32 +// TopEntries represents the response of `>top-clients` and `>top-domains` commands. +// It contains a total amount of entries and a list of entries label and count +type TopEntries struct { + Total int + Entries []entry } -type Int32Block struct { - _ uint8 - Value int32 -} - -type UInt64Block struct { - _ uint8 - Value uint64 -} - -type UInt8Block struct { - _ uint8 - Value uint8 -} - -type Float32Block struct { - _ uint8 - Value float32 -} - -type Entries struct { - Total UInt32Block - List []struct { - Entry string - Count uint32 - } +type entry struct { + Label string + Count int } +// UpstreamDestination represents the response `>forward-dest` command. +// It contains a name, address and percentage of total requests type UpstreamDestination struct { Name string Address string Percentage float32 } -type TimestampCount struct { - Timestamp UInt32Block - Count UInt32Block -} - +// TimestampClients represents the response `>ClientsoverTime` command. +// It contains a timestamp and a list of amount of requests made by each client. +// Order of requests counts represents clients from `>client-names` command type TimestampClients struct { - Timestamp uint32 - Count []Int32Block -} - -type ClientsOverTime struct { - List []TimestampClients + Timestamp int + Count []int } +// Client represents the response `>client-names` command. +// It contains a name and address of the client type Client struct { Name string Address string } -type OverTime struct { - Forwarded []TimestampCount - Blocked []TimestampCount +// QueriesOverTime represents the response `>overTime` command. +// It contains list of amounts of forwarded and blocked requests grouped by 10 minute intervals +type QueriesOverTime struct { + Forwarded []timestampCount + Blocked []timestampCount +} + +type timestampCount struct { + Timestamp int + Count int +} + +type ftlInt32 struct { + _ uint8 + Value int32 +} + +type ftlInt8 struct { + _ uint8 + Value uint8 +} + +type ftlFloat32 struct { + _ uint8 + Value float32 } diff --git a/client/queries_over_time.go b/client/queries_over_time.go index e8a77bc..efa41e7 100644 --- a/client/queries_over_time.go +++ b/client/queries_over_time.go @@ -14,14 +14,13 @@ package client import ( - "encoding/binary" "net" ) // GetQueriesOverTime retrieves amount of allowed and blocked queries // for the last 24 hours aggregated over 10 minute intervals // from response of `>overTime` command -func (client *FTLClient) GetQueriesOverTime() (*OverTime, error) { +func (client *FTLClient) GetQueriesOverTime() (*QueriesOverTime, error) { conn, err := net.DialUnix("unix", nil, client.addr) if err != nil { return nil, err @@ -32,32 +31,51 @@ func (client *FTLClient) GetQueriesOverTime() (*OverTime, error) { return nil, err } - var lines struct { - _ uint8 - Lines uint16 - } - if err := binary.Read(conn, binary.BigEndian, &lines); err != nil { + var result QueriesOverTime + + lines, err := readMapCount(conn) + if err != nil { return nil, err } - forwarded := make([]TimestampCount, lines.Lines) + for i := 0; i < lines; i++ { + timestamp, err := readInt32(conn) + if err != nil { + return nil, err + } - if err := binary.Read(conn, binary.BigEndian, &forwarded); err != nil { - return nil, err + count, err := readInt32(conn) + if err != nil { + return nil, err + } + + result.Forwarded = append(result.Forwarded, timestampCount{ + Timestamp: timestamp, + Count: count, + }) } - if err := binary.Read(conn, binary.BigEndian, &lines); err != nil { + lines, err = readMapCount(conn) + if err != nil { return nil, err } - blocked := make([]TimestampCount, lines.Lines) + for i := 0; i < lines; i++ { + timestamp, err := readInt32(conn) + if err != nil { + return nil, err + } - if err := binary.Read(conn, binary.BigEndian, &blocked); err != nil { - return nil, err + count, err := readInt32(conn) + if err != nil { + return nil, err + } + + result.Blocked = append(result.Blocked, timestampCount{ + Timestamp: timestamp, + Count: count, + }) } - return &OverTime{ - Forwarded: forwarded, - Blocked: blocked, - }, nil + return &result, nil } diff --git a/client/query_types.go b/client/query_types.go index dc48627..68d52c9 100644 --- a/client/query_types.go +++ b/client/query_types.go @@ -33,7 +33,7 @@ func (client *FTLClient) GetQueryTypes() (*map[string]float32, error) { queryTypes := make(map[string]float32) for { name, err := readString(conn) - if err == EOF { + if err == errEndOfInput { break } if err != nil { diff --git a/client/stats.go b/client/stats.go index a74797e..4255dc5 100644 --- a/client/stats.go +++ b/client/stats.go @@ -31,16 +31,16 @@ func (client *FTLClient) GetStats() (*Stats, error) { } var stats struct { - DomainsBeingBlocked UInt32Block - DnsQueriesToday UInt32Block - AdsBlockedToday UInt32Block - AdsPercentageToday Float32Block - UniqueDomains UInt32Block - QueriesForwarded UInt32Block - QueriesCached UInt32Block - ClientsEverSeen UInt32Block - UniqueClients UInt32Block - Status UInt8Block + DomainsBeingBlocked ftlInt32 + DnsQueriesToday ftlInt32 + AdsBlockedToday ftlInt32 + AdsPercentageToday ftlFloat32 + UniqueDomains ftlInt32 + QueriesForwarded ftlInt32 + QueriesCached ftlInt32 + ClientsEverSeen ftlInt32 + UniqueClients ftlInt32 + Status ftlInt8 } if err := binary.Read(conn, binary.BigEndian, &stats); err != nil { return nil, err diff --git a/collector/ad_domains.go b/collector/ad_domains.go index dec4165..d97edf3 100644 --- a/collector/ad_domains.go +++ b/collector/ad_domains.go @@ -49,10 +49,10 @@ func (c *adDomainCollector) update(client *client.FTLClient, ch chan<- prometheu return err } - ch <- prometheus.MustNewConstMetric(c.totalAdDomainsToday, prometheus.GaugeValue, float64(queries.Total.Value)) + ch <- prometheus.MustNewConstMetric(c.totalAdDomainsToday, prometheus.GaugeValue, float64(queries.Total)) - for _, hits := range queries.List { - ch <- prometheus.MustNewConstMetric(c.topAdDomainsToday, prometheus.GaugeValue, float64(hits.Count), hits.Entry) + for _, hits := range queries.Entries { + ch <- prometheus.MustNewConstMetric(c.topAdDomainsToday, prometheus.GaugeValue, float64(hits.Count), hits.Label) } return nil diff --git a/collector/clients.go b/collector/clients.go index 2294f49..99481c9 100644 --- a/collector/clients.go +++ b/collector/clients.go @@ -49,8 +49,8 @@ func (c *clientCollector) update(client *client.FTLClient, ch chan<- prometheus. return err } - for _, hits := range clients.List { - ch <- prometheus.MustNewConstMetric(c.topClientsToday, prometheus.GaugeValue, float64(hits.Count), hits.Entry) + for _, hits := range clients.Entries { + ch <- prometheus.MustNewConstMetric(c.topClientsToday, prometheus.GaugeValue, float64(hits.Count), hits.Label) } blockedClients, err := client.GetTopBlockedClients() @@ -58,8 +58,8 @@ func (c *clientCollector) update(client *client.FTLClient, ch chan<- prometheus. return err } - for _, hits := range blockedClients.List { - ch <- prometheus.MustNewConstMetric(c.topBlockedClientsToday, prometheus.GaugeValue, float64(hits.Count), hits.Entry) + for _, hits := range blockedClients.Entries { + ch <- prometheus.MustNewConstMetric(c.topBlockedClientsToday, prometheus.GaugeValue, float64(hits.Count), hits.Label) } return nil diff --git a/collector/clients_over_time.go b/collector/clients_over_time.go index 3a604fc..0bf15dd 100644 --- a/collector/clients_over_time.go +++ b/collector/clients_over_time.go @@ -64,7 +64,7 @@ func (c *clientsOverTimeCollector) update(client *client.FTLClient, ch chan<- pr ch <- prometheus.MustNewConstMetric( c.clients, prometheus.GaugeValue, - float64(count.Value), + float64(count), address, ) } diff --git a/collector/collector.go b/collector/collector.go index a89e35a..8721030 100644 --- a/collector/collector.go +++ b/collector/collector.go @@ -46,6 +46,7 @@ var ( ) ) +// registerCollector registers and enables collectors based on flags provided func registerCollector(collector string, isDefaultEnabled bool, factory func() (Collector, error)) { var helpDefaultState string if isDefaultEnabled { @@ -92,14 +93,14 @@ func NewExporter(socket string) (*Exporter, error) { } } - client, err := client.NewClient(socket) + ftlClient, err := client.NewClient(socket) if err != nil { return nil, err } return &Exporter{ collectors: collectors, - client: client, + client: ftlClient, }, nil } @@ -116,6 +117,7 @@ func (collector Exporter) Collect(ch chan<- prometheus.Metric) { } } +// execute runs the collector's update function, sets duration and success metrics for collector func execute(name string, c Collector, client *client.FTLClient, ch chan<- prometheus.Metric) { begin := time.Now() err := c.update(client, ch) diff --git a/collector/db_stats.go b/collector/db_stats.go index c74431c..37bd980 100644 --- a/collector/db_stats.go +++ b/collector/db_stats.go @@ -51,8 +51,8 @@ func (c *dbStatsCollector) update(client *client.FTLClient, ch chan<- prometheus return err } - ch <- prometheus.MustNewConstMetric(c.queriesInDatabase, prometheus.CounterValue, float64(dbStats.Rows.Value)) - ch <- prometheus.MustNewConstMetric(c.databaseFileSize, prometheus.CounterValue, float64(dbStats.Size.Value)) + ch <- prometheus.MustNewConstMetric(c.queriesInDatabase, prometheus.CounterValue, float64(dbStats.RowsCount)) + ch <- prometheus.MustNewConstMetric(c.databaseFileSize, prometheus.CounterValue, float64(dbStats.FileSize)) return nil } diff --git a/collector/domains.go b/collector/domains.go index 6593389..a46c26c 100644 --- a/collector/domains.go +++ b/collector/domains.go @@ -49,10 +49,10 @@ func (c *domainCollector) update(client *client.FTLClient, ch chan<- prometheus. return err } - ch <- prometheus.MustNewConstMetric(c.totalDomainsToday, prometheus.GaugeValue, float64(queries.Total.Value)) + ch <- prometheus.MustNewConstMetric(c.totalDomainsToday, prometheus.GaugeValue, float64(queries.Total)) - for _, hits := range queries.List { - ch <- prometheus.MustNewConstMetric(c.topDomainsToday, prometheus.GaugeValue, float64(hits.Count), hits.Entry) + for _, hits := range queries.Entries { + ch <- prometheus.MustNewConstMetric(c.topDomainsToday, prometheus.GaugeValue, float64(hits.Count), hits.Label) } return nil diff --git a/collector/queries_over_time.go b/collector/queries_over_time.go index a6db411..747bc09 100644 --- a/collector/queries_over_time.go +++ b/collector/queries_over_time.go @@ -51,19 +51,19 @@ func (c *queriesOverTimeCollector) update(client *client.FTLClient, ch chan<- pr } sort.SliceStable(queriesOverTime.Forwarded, func(i, j int) bool { - return queriesOverTime.Forwarded[i].Timestamp.Value > queriesOverTime.Forwarded[j].Timestamp.Value + return queriesOverTime.Forwarded[i].Timestamp > queriesOverTime.Forwarded[j].Timestamp }) lastForwardedOverTime := queriesOverTime.Forwarded[:1] for _, hits := range lastForwardedOverTime { - ch <- prometheus.MustNewConstMetric(c.queriesForwarded, prometheus.GaugeValue, float64(hits.Count.Value)) + ch <- prometheus.MustNewConstMetric(c.queriesForwarded, prometheus.GaugeValue, float64(hits.Count)) } sort.SliceStable(queriesOverTime.Blocked, func(i, j int) bool { - return queriesOverTime.Blocked[i].Timestamp.Value > queriesOverTime.Blocked[j].Timestamp.Value + return queriesOverTime.Blocked[i].Timestamp > queriesOverTime.Blocked[j].Timestamp }) lastBlockedOverTime := queriesOverTime.Blocked[:1] for _, hits := range lastBlockedOverTime { - ch <- prometheus.MustNewConstMetric(c.queriesBlocked, prometheus.GaugeValue, float64(hits.Count.Value)) + ch <- prometheus.MustNewConstMetric(c.queriesBlocked, prometheus.GaugeValue, float64(hits.Count)) } return nil