Skip to content

Commit cc50a39

Browse files
committed
feat: implement nfqueue collector
Signed-off-by: Denis Voytyuk <5462781+denisvmedia@users.noreply.github.com>
1 parent b5966ca commit cc50a39

3 files changed

Lines changed: 210 additions & 0 deletions

File tree

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
0 31621 0 2 65531 0 0 50 1
2+
1 31622 100 1 1024 150 10 200 1
3+
2 31623 25 0 512 20 5 300 1

collector/nfqueue_linux.go

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
// Copyright The Prometheus Authors
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
//go:build !nonfqueue
15+
16+
package collector
17+
18+
import (
19+
"errors"
20+
"fmt"
21+
"log/slog"
22+
"os"
23+
"strconv"
24+
25+
"github.com/prometheus/client_golang/prometheus"
26+
"github.com/prometheus/procfs"
27+
)
28+
29+
type nfqueueCollector struct {
30+
fs procfs.FS
31+
queueTotal *prometheus.Desc
32+
dropped *prometheus.Desc
33+
info *prometheus.Desc
34+
logger *slog.Logger
35+
}
36+
37+
func init() {
38+
registerCollector("nfqueue", defaultDisabled, NewNFQueueCollector)
39+
}
40+
41+
// NewNFQueueCollector returns a new Collector exposing netfilter queue stats
42+
// from /proc/net/netfilter/nfnetlink_queue.
43+
func NewNFQueueCollector(logger *slog.Logger) (Collector, error) {
44+
fs, err := procfs.NewFS(*procPath)
45+
if err != nil {
46+
return nil, fmt.Errorf("failed to open procfs: %w", err)
47+
}
48+
49+
return &nfqueueCollector{
50+
fs: fs,
51+
queueTotal: prometheus.NewDesc(
52+
prometheus.BuildFQName(namespace, "nfqueue", "queue_total"),
53+
"Current number of packets waiting in the queue.",
54+
[]string{"queue"}, nil,
55+
),
56+
dropped: prometheus.NewDesc(
57+
prometheus.BuildFQName(namespace, "nfqueue", "dropped_total"),
58+
"Total number of packets dropped.",
59+
[]string{"queue", "reason"}, nil,
60+
),
61+
info: prometheus.NewDesc(
62+
prometheus.BuildFQName(namespace, "nfqueue", "info"),
63+
"Non-numeric metadata about the queue (value is always 1).",
64+
[]string{"queue", "peer_portid", "copy_mode", "copy_range"}, nil,
65+
),
66+
logger: logger,
67+
}, nil
68+
}
69+
70+
func (c *nfqueueCollector) Update(ch chan<- prometheus.Metric) error {
71+
queues, err := c.fs.NFNetLinkQueue()
72+
if err != nil {
73+
if errors.Is(err, os.ErrNotExist) {
74+
c.logger.Debug("nfqueue: file not found, NFQUEUE probably not in use")
75+
return ErrNoData
76+
}
77+
return fmt.Errorf("failed to retrieve nfqueue stats: %w", err)
78+
}
79+
80+
for _, q := range queues {
81+
queueID := strconv.FormatUint(uint64(q.QueueID), 10)
82+
ch <- prometheus.MustNewConstMetric(
83+
c.queueTotal, prometheus.GaugeValue,
84+
float64(q.QueueTotal), queueID,
85+
)
86+
ch <- prometheus.MustNewConstMetric(
87+
c.dropped, prometheus.CounterValue,
88+
float64(q.QueueDropped), queueID, "queue_full",
89+
)
90+
ch <- prometheus.MustNewConstMetric(
91+
c.dropped, prometheus.CounterValue,
92+
float64(q.QueueUserDropped), queueID, "user",
93+
)
94+
ch <- prometheus.MustNewConstMetric(
95+
c.info, prometheus.GaugeValue, 1,
96+
queueID,
97+
strconv.FormatUint(uint64(q.PeerPID), 10),
98+
nfqueueCopyModeString(q.CopyMode),
99+
strconv.FormatUint(uint64(q.CopyRange), 10),
100+
)
101+
}
102+
return nil
103+
}
104+
105+
func nfqueueCopyModeString(mode uint) string {
106+
switch mode {
107+
case 0:
108+
return "none"
109+
case 1:
110+
return "meta"
111+
case 2:
112+
return "packet"
113+
default:
114+
return strconv.FormatUint(uint64(mode), 10)
115+
}
116+
}

collector/nfqueue_linux_test.go

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// Copyright The Prometheus Authors
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
//go:build !nonfqueue
15+
16+
package collector
17+
18+
import (
19+
"errors"
20+
"io"
21+
"log/slog"
22+
"strings"
23+
"testing"
24+
25+
"github.com/prometheus/client_golang/prometheus"
26+
"github.com/prometheus/client_golang/prometheus/testutil"
27+
)
28+
29+
type testNFQueueCollector struct {
30+
nc Collector
31+
}
32+
33+
func (c testNFQueueCollector) Collect(ch chan<- prometheus.Metric) {
34+
c.nc.Update(ch)
35+
}
36+
37+
func (c testNFQueueCollector) Describe(ch chan<- *prometheus.Desc) {
38+
prometheus.DescribeByCollect(c, ch)
39+
}
40+
41+
func TestNFQueueStats(t *testing.T) {
42+
testcase := `# HELP node_nfqueue_dropped_total Total number of packets dropped.
43+
# TYPE node_nfqueue_dropped_total counter
44+
node_nfqueue_dropped_total{queue="0",reason="queue_full"} 0
45+
node_nfqueue_dropped_total{queue="0",reason="user"} 0
46+
node_nfqueue_dropped_total{queue="1",reason="queue_full"} 150
47+
node_nfqueue_dropped_total{queue="1",reason="user"} 10
48+
node_nfqueue_dropped_total{queue="2",reason="queue_full"} 20
49+
node_nfqueue_dropped_total{queue="2",reason="user"} 5
50+
# HELP node_nfqueue_info Non-numeric metadata about the queue (value is always 1).
51+
# TYPE node_nfqueue_info gauge
52+
node_nfqueue_info{copy_mode="packet",copy_range="65531",peer_portid="31621",queue="0"} 1
53+
node_nfqueue_info{copy_mode="meta",copy_range="1024",peer_portid="31622",queue="1"} 1
54+
node_nfqueue_info{copy_mode="none",copy_range="512",peer_portid="31623",queue="2"} 1
55+
# HELP node_nfqueue_queue_total Current number of packets waiting in the queue.
56+
# TYPE node_nfqueue_queue_total gauge
57+
node_nfqueue_queue_total{queue="0"} 0
58+
node_nfqueue_queue_total{queue="1"} 100
59+
node_nfqueue_queue_total{queue="2"} 25
60+
`
61+
*procPath = "fixtures/proc"
62+
63+
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
64+
c, err := NewNFQueueCollector(logger)
65+
if err != nil {
66+
t.Fatal(err)
67+
}
68+
reg := prometheus.NewRegistry()
69+
reg.MustRegister(&testNFQueueCollector{nc: c})
70+
71+
err = testutil.GatherAndCompare(reg, strings.NewReader(testcase))
72+
if err != nil {
73+
t.Fatal(err)
74+
}
75+
}
76+
77+
func TestNFQueueStatsErrNoData(t *testing.T) {
78+
*procPath = t.TempDir() // valid dir, but no nfnetlink_queue file inside
79+
80+
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
81+
c, err := NewNFQueueCollector(logger)
82+
if err != nil {
83+
t.Fatal(err)
84+
}
85+
86+
ch := make(chan prometheus.Metric)
87+
err = c.Update(ch)
88+
if !errors.Is(err, ErrNoData) {
89+
t.Fatalf("expected ErrNoData, got: %v", err)
90+
}
91+
}

0 commit comments

Comments
 (0)