Skip to content

Commit aed9afd

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

File tree

3 files changed

+210
-0
lines changed

3 files changed

+210
-0
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
0 31621 0 2 65531 0 0 50 1
2+
1 31622 100 1 1024 150 10 200 1

collector/nfqueue_linux.go

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
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+
queueDropped *prometheus.Desc
33+
userDropped *prometheus.Desc
34+
idSequence *prometheus.Desc
35+
info *prometheus.Desc
36+
logger *slog.Logger
37+
}
38+
39+
func init() {
40+
registerCollector("nfqueue", defaultDisabled, NewNFQueueCollector)
41+
}
42+
43+
// NewNFQueueCollector returns a new Collector exposing netfilter queue stats
44+
// from /proc/net/netfilter/nfnetlink_queue.
45+
func NewNFQueueCollector(logger *slog.Logger) (Collector, error) {
46+
fs, err := procfs.NewFS(*procPath)
47+
if err != nil {
48+
return nil, fmt.Errorf("failed to open procfs: %w", err)
49+
}
50+
51+
return &nfqueueCollector{
52+
fs: fs,
53+
queueTotal: prometheus.NewDesc(
54+
prometheus.BuildFQName(namespace, "nfqueue", "queue_total"),
55+
"Current number of packets waiting in the queue.",
56+
[]string{"queue"}, nil,
57+
),
58+
queueDropped: prometheus.NewDesc(
59+
prometheus.BuildFQName(namespace, "nfqueue", "queue_dropped_total"),
60+
"Packets dropped because the queue was full.",
61+
[]string{"queue"}, nil,
62+
),
63+
userDropped: prometheus.NewDesc(
64+
prometheus.BuildFQName(namespace, "nfqueue", "user_dropped_total"),
65+
"Packets dropped due to a failure to send to userspace.",
66+
[]string{"queue"}, nil,
67+
),
68+
idSequence: prometheus.NewDesc(
69+
prometheus.BuildFQName(namespace, "nfqueue", "id_sequence_total"),
70+
"The current packet ID sequence number.",
71+
[]string{"queue"}, nil,
72+
),
73+
info: prometheus.NewDesc(
74+
prometheus.BuildFQName(namespace, "nfqueue", "info"),
75+
"Non-numeric metadata about the queue (value is always 1).",
76+
[]string{"queue", "peer_portid", "copy_mode", "copy_range"}, nil,
77+
),
78+
logger: logger,
79+
}, nil
80+
}
81+
82+
func (c *nfqueueCollector) Update(ch chan<- prometheus.Metric) error {
83+
queues, err := c.fs.NFNetLinkQueue()
84+
if err != nil {
85+
if errors.Is(err, os.ErrNotExist) {
86+
c.logger.Debug("nfqueue: file not found, NFQUEUE probably not in use")
87+
return ErrNoData
88+
}
89+
return fmt.Errorf("failed to retrieve nfqueue stats: %w", err)
90+
}
91+
92+
for _, q := range queues {
93+
queueID := strconv.FormatUint(uint64(q.QueueID), 10)
94+
ch <- prometheus.MustNewConstMetric(
95+
c.queueTotal, prometheus.GaugeValue,
96+
float64(q.QueueTotal), queueID,
97+
)
98+
ch <- prometheus.MustNewConstMetric(
99+
c.queueDropped, prometheus.CounterValue,
100+
float64(q.QueueDropped), queueID,
101+
)
102+
ch <- prometheus.MustNewConstMetric(
103+
c.userDropped, prometheus.CounterValue,
104+
float64(q.QueueUserDropped), queueID,
105+
)
106+
ch <- prometheus.MustNewConstMetric(
107+
c.idSequence, prometheus.CounterValue,
108+
float64(q.SequenceID), queueID,
109+
)
110+
ch <- prometheus.MustNewConstMetric(
111+
c.info, prometheus.GaugeValue, 1,
112+
queueID,
113+
strconv.FormatUint(uint64(q.PeerPID), 10),
114+
nfqueueCopyModeString(q.CopyMode),
115+
strconv.FormatUint(uint64(q.CopyRange), 10),
116+
)
117+
}
118+
return nil
119+
}
120+
121+
func nfqueueCopyModeString(mode uint) string {
122+
switch mode {
123+
case 0:
124+
return "none"
125+
case 1:
126+
return "meta"
127+
case 2:
128+
return "packet"
129+
default:
130+
return strconv.FormatUint(uint64(mode), 10)
131+
}
132+
}

collector/nfqueue_linux_test.go

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
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+
"io"
20+
"log/slog"
21+
"strings"
22+
"testing"
23+
24+
"github.com/prometheus/client_golang/prometheus"
25+
"github.com/prometheus/client_golang/prometheus/testutil"
26+
)
27+
28+
type testNFQueueCollector struct {
29+
nc Collector
30+
}
31+
32+
func (c testNFQueueCollector) Collect(ch chan<- prometheus.Metric) {
33+
c.nc.Update(ch)
34+
}
35+
36+
func (c testNFQueueCollector) Describe(ch chan<- *prometheus.Desc) {
37+
prometheus.DescribeByCollect(c, ch)
38+
}
39+
40+
func TestNFQueueStats(t *testing.T) {
41+
testcase := `# HELP node_nfqueue_id_sequence_total The current packet ID sequence number.
42+
# TYPE node_nfqueue_id_sequence_total counter
43+
node_nfqueue_id_sequence_total{queue="0"} 50
44+
node_nfqueue_id_sequence_total{queue="1"} 200
45+
# HELP node_nfqueue_info Non-numeric metadata about the queue (value is always 1).
46+
# TYPE node_nfqueue_info gauge
47+
node_nfqueue_info{copy_mode="packet",copy_range="65531",peer_portid="31621",queue="0"} 1
48+
node_nfqueue_info{copy_mode="meta",copy_range="1024",peer_portid="31622",queue="1"} 1
49+
# HELP node_nfqueue_queue_dropped_total Packets dropped because the queue was full.
50+
# TYPE node_nfqueue_queue_dropped_total counter
51+
node_nfqueue_queue_dropped_total{queue="0"} 0
52+
node_nfqueue_queue_dropped_total{queue="1"} 150
53+
# HELP node_nfqueue_queue_total Current number of packets waiting in the queue.
54+
# TYPE node_nfqueue_queue_total gauge
55+
node_nfqueue_queue_total{queue="0"} 0
56+
node_nfqueue_queue_total{queue="1"} 100
57+
# HELP node_nfqueue_user_dropped_total Packets dropped due to a failure to send to userspace.
58+
# TYPE node_nfqueue_user_dropped_total counter
59+
node_nfqueue_user_dropped_total{queue="0"} 0
60+
node_nfqueue_user_dropped_total{queue="1"} 10
61+
`
62+
*procPath = "fixtures/proc"
63+
64+
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
65+
c, err := NewNFQueueCollector(logger)
66+
if err != nil {
67+
t.Fatal(err)
68+
}
69+
reg := prometheus.NewRegistry()
70+
reg.MustRegister(&testNFQueueCollector{nc: c})
71+
72+
err = testutil.GatherAndCompare(reg, strings.NewReader(testcase))
73+
if err != nil {
74+
t.Fatal(err)
75+
}
76+
}

0 commit comments

Comments
 (0)