Skip to content

Commit 771c3d4

Browse files
authored
Add /proc/swaps collector (#3428)
* Add /proc/swaps collector Building on prometheus/procfs#246 this PR is introducing metrics around swap devices. Today metrics around swap are already available, but they are on the node level. With this PR, metrics are now available per device. Relates to: #1890 Signed-off-by: Fabian Deutsch <[email protected]> Co-authored-by: claude.ai --------- Signed-off-by: Fabian Deutsch <[email protected]>
1 parent b6112ea commit 771c3d4

File tree

4 files changed

+190
-0
lines changed

4 files changed

+190
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ qdisc | Exposes [queuing discipline](https://en.wikipedia.org/wiki/Network_sched
208208
slabinfo | Exposes slab statistics from `/proc/slabinfo`. Note that permission of `/proc/slabinfo` is usually 0400, so set it appropriately. | Linux
209209
softirqs | Exposes detailed softirq statistics from `/proc/softirqs`. | Linux
210210
sysctl | Expose sysctl values from `/proc/sys`. Use `--collector.sysctl.include(-info)` to configure. | Linux
211+
swap | Expose swap information from `/proc/swaps`. | Linux
211212
systemd | Exposes service and system status from [systemd](http://www.freedesktop.org/wiki/Software/systemd/). | Linux
212213
tcpstat | Exposes TCP connection status information from `/proc/net/tcp` and `/proc/net/tcp6`. (Warning: the current version has potential performance issues in high load situations.) | Linux
213214
wifi | Exposes WiFi device and station statistics. | Linux

collector/fixtures/proc/swaps

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Filename Type Size Used Priority
2+
/dev/zram0 partition 8388604 76 100

collector/swap_linux.go

Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
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 !noswap
15+
16+
package collector
17+
18+
import (
19+
"fmt"
20+
"log/slog"
21+
22+
"github.com/prometheus/client_golang/prometheus"
23+
"github.com/prometheus/procfs"
24+
)
25+
26+
const (
27+
swapSubsystem = "swap"
28+
)
29+
30+
var swapLabelNames = []string{"device", "swap_type"}
31+
32+
type swapCollector struct {
33+
fs procfs.FS
34+
logger *slog.Logger
35+
}
36+
37+
func init() {
38+
registerCollector("swap", defaultDisabled, NewSwapCollector)
39+
}
40+
41+
// NewSwapCollector returns a new Collector exposing swap device statistics.
42+
func NewSwapCollector(logger *slog.Logger) (Collector, error) {
43+
fs, err := procfs.NewFS(*procPath)
44+
if err != nil {
45+
return nil, fmt.Errorf("failed to open procfs: %w", err)
46+
}
47+
48+
return &swapCollector{
49+
fs: fs,
50+
logger: logger,
51+
}, nil
52+
}
53+
54+
type SwapsEntry struct {
55+
Device string
56+
Type string
57+
Priority int
58+
Size int
59+
Used int
60+
}
61+
62+
func (c *swapCollector) getSwapInfo() ([]SwapsEntry, error) {
63+
swaps, err := c.fs.Swaps()
64+
if err != nil {
65+
return nil, fmt.Errorf("couldn't get proc/swap information: %w", err)
66+
}
67+
68+
metrics := make([]SwapsEntry, 0, len(swaps))
69+
70+
for _, swap := range swaps {
71+
metrics = append(metrics, SwapsEntry{Device: swap.Filename, Type: swap.Type,
72+
Priority: swap.Priority, Size: swap.Size, Used: swap.Used})
73+
}
74+
75+
return metrics, nil
76+
}
77+
78+
func (c *swapCollector) Update(ch chan<- prometheus.Metric) error {
79+
swaps, err := c.getSwapInfo()
80+
if err != nil {
81+
return fmt.Errorf("couldn't get swap information: %w", err)
82+
}
83+
84+
for _, swap := range swaps {
85+
swapLabelValues := []string{swap.Device, swap.Type}
86+
87+
// Export swap size in bytes
88+
ch <- prometheus.MustNewConstMetric(
89+
prometheus.NewDesc(
90+
prometheus.BuildFQName(namespace, swapSubsystem, "size_bytes"),
91+
"Swap device size in bytes.",
92+
[]string{"device", "swap_type"}, nil,
93+
),
94+
prometheus.GaugeValue,
95+
// Size is provided in kbytes (not bytes), translate to bytes
96+
// see https://github.com/torvalds/linux/blob/fd94619c43360eb44d28bd3ef326a4f85c600a07/mm/swapfile.c#L3079-L3080
97+
float64(swap.Size*1024),
98+
swapLabelValues...,
99+
)
100+
101+
// Export swap used in bytes
102+
ch <- prometheus.MustNewConstMetric(
103+
prometheus.NewDesc(
104+
prometheus.BuildFQName(namespace, swapSubsystem, "used_bytes"),
105+
"Swap device used in bytes.",
106+
swapLabelNames, nil,
107+
),
108+
prometheus.GaugeValue,
109+
// Swap used is also provided in kbytes, translate to bytes
110+
float64(swap.Used*1024),
111+
swapLabelValues...,
112+
)
113+
114+
// Export swap priority
115+
ch <- prometheus.MustNewConstMetric(
116+
prometheus.NewDesc(
117+
prometheus.BuildFQName(namespace, swapSubsystem, "priority"),
118+
"Swap device priority.",
119+
swapLabelNames, nil,
120+
),
121+
prometheus.GaugeValue,
122+
float64(swap.Priority),
123+
swapLabelValues...,
124+
)
125+
126+
}
127+
128+
return nil
129+
}

collector/swap_linux_test.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
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 !noswap
15+
// +build !noswap
16+
17+
package collector
18+
19+
import (
20+
"io"
21+
"log/slog"
22+
"testing"
23+
)
24+
25+
func TestSwap(t *testing.T) {
26+
*procPath = "fixtures/proc"
27+
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
28+
29+
collector, err := NewSwapCollector(logger)
30+
if err != nil {
31+
panic(err)
32+
}
33+
34+
swapInfo, err := collector.(*swapCollector).getSwapInfo()
35+
if err != nil {
36+
panic(err)
37+
}
38+
39+
if want, got := "/dev/zram0", swapInfo[0].Device; want != got {
40+
t.Errorf("want swap device %s, got %s", want, got)
41+
}
42+
43+
if want, got := "partition", swapInfo[0].Type; want != got {
44+
t.Errorf("want swap type %s, got %s", want, got)
45+
}
46+
47+
if want, got := 100, swapInfo[0].Priority; want != got {
48+
t.Errorf("want swap priority %d, got %d", want, got)
49+
}
50+
51+
if want, got := 8388604, swapInfo[0].Size; want != got {
52+
t.Errorf("want swap size %d, got %d", want, got)
53+
}
54+
55+
if want, got := 76, swapInfo[0].Used; want != got {
56+
t.Errorf("want swpa used %d, got %d", want, got)
57+
}
58+
}

0 commit comments

Comments
 (0)