Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions collector/fixtures/e2e-64k-page-output.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3778,30 +3778,40 @@ node_slabinfo_active_objects{slab="dmaengine-unmap-128"} 1206
node_slabinfo_active_objects{slab="kmalloc-8192"} 132
node_slabinfo_active_objects{slab="kmem_cache"} 320
node_slabinfo_active_objects{slab="tw_sock_TCP"} 704
node_slabinfo_active_objects{index="0",slab="mlx5_fs_ftes"} 44
node_slabinfo_active_objects{index="1",slab="mlx5_fs_ftes"} 396
# HELP node_slabinfo_object_size_bytes The size of objects in this slab, in bytes.
# TYPE node_slabinfo_object_size_bytes gauge
node_slabinfo_object_size_bytes{slab="dmaengine-unmap-128"} 1088
node_slabinfo_object_size_bytes{slab="kmalloc-8192"} 8192
node_slabinfo_object_size_bytes{slab="kmem_cache"} 256
node_slabinfo_object_size_bytes{slab="tw_sock_TCP"} 256
node_slabinfo_object_size_bytes{index="0",slab="mlx5_fs_ftes"} 736
node_slabinfo_object_size_bytes{index="1",slab="mlx5_fs_ftes"} 736
# HELP node_slabinfo_objects The total number of allocated objects (i.e., objects that are both in use and not in use).
# TYPE node_slabinfo_objects gauge
node_slabinfo_objects{slab="dmaengine-unmap-128"} 1320
node_slabinfo_objects{slab="kmalloc-8192"} 148
node_slabinfo_objects{slab="kmem_cache"} 320
node_slabinfo_objects{slab="tw_sock_TCP"} 864
node_slabinfo_objects{index="0",slab="mlx5_fs_ftes"} 44
node_slabinfo_objects{index="1",slab="mlx5_fs_ftes"} 396
# HELP node_slabinfo_objects_per_slab The number of objects stored in each slab.
# TYPE node_slabinfo_objects_per_slab gauge
node_slabinfo_objects_per_slab{slab="dmaengine-unmap-128"} 30
node_slabinfo_objects_per_slab{slab="kmalloc-8192"} 4
node_slabinfo_objects_per_slab{slab="kmem_cache"} 32
node_slabinfo_objects_per_slab{slab="tw_sock_TCP"} 32
node_slabinfo_objects_per_slab{index="0",slab="mlx5_fs_ftes"} 44
node_slabinfo_objects_per_slab{index="1",slab="mlx5_fs_ftes"} 44
# HELP node_slabinfo_pages_per_slab The number of pages allocated for each slab.
# TYPE node_slabinfo_pages_per_slab gauge
node_slabinfo_pages_per_slab{slab="dmaengine-unmap-128"} 8
node_slabinfo_pages_per_slab{slab="kmalloc-8192"} 8
node_slabinfo_pages_per_slab{slab="kmem_cache"} 2
node_slabinfo_pages_per_slab{slab="tw_sock_TCP"} 2
node_slabinfo_pages_per_slab{index="0",slab="mlx5_fs_ftes"} 8
node_slabinfo_pages_per_slab{index="1",slab="mlx5_fs_ftes"} 8
# HELP node_sockstat_FRAG6_inuse Number of FRAG6 sockets in state inuse.
# TYPE node_sockstat_FRAG6_inuse gauge
node_sockstat_FRAG6_inuse 0
Expand Down
10 changes: 10 additions & 0 deletions collector/fixtures/e2e-output.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3810,30 +3810,40 @@ node_slabinfo_active_objects{slab="dmaengine-unmap-128"} 1206
node_slabinfo_active_objects{slab="kmalloc-8192"} 132
node_slabinfo_active_objects{slab="kmem_cache"} 320
node_slabinfo_active_objects{slab="tw_sock_TCP"} 704
node_slabinfo_active_objects{index="0",slab="mlx5_fs_ftes"} 44
node_slabinfo_active_objects{index="1",slab="mlx5_fs_ftes"} 396
# HELP node_slabinfo_object_size_bytes The size of objects in this slab, in bytes.
# TYPE node_slabinfo_object_size_bytes gauge
node_slabinfo_object_size_bytes{slab="dmaengine-unmap-128"} 1088
node_slabinfo_object_size_bytes{slab="kmalloc-8192"} 8192
node_slabinfo_object_size_bytes{slab="kmem_cache"} 256
node_slabinfo_object_size_bytes{slab="tw_sock_TCP"} 256
node_slabinfo_object_size_bytes{index="0",slab="mlx5_fs_ftes"} 736
node_slabinfo_object_size_bytes{index="1",slab="mlx5_fs_ftes"} 736
# HELP node_slabinfo_objects The total number of allocated objects (i.e., objects that are both in use and not in use).
# TYPE node_slabinfo_objects gauge
node_slabinfo_objects{slab="dmaengine-unmap-128"} 1320
node_slabinfo_objects{slab="kmalloc-8192"} 148
node_slabinfo_objects{slab="kmem_cache"} 320
node_slabinfo_objects{slab="tw_sock_TCP"} 864
node_slabinfo_objects{index="0",slab="mlx5_fs_ftes"} 44
node_slabinfo_objects{index="1",slab="mlx5_fs_ftes"} 396
# HELP node_slabinfo_objects_per_slab The number of objects stored in each slab.
# TYPE node_slabinfo_objects_per_slab gauge
node_slabinfo_objects_per_slab{slab="dmaengine-unmap-128"} 30
node_slabinfo_objects_per_slab{slab="kmalloc-8192"} 4
node_slabinfo_objects_per_slab{slab="kmem_cache"} 32
node_slabinfo_objects_per_slab{slab="tw_sock_TCP"} 32
node_slabinfo_objects_per_slab{index="0",slab="mlx5_fs_ftes"} 44
node_slabinfo_objects_per_slab{index="1",slab="mlx5_fs_ftes"} 44
# HELP node_slabinfo_pages_per_slab The number of pages allocated for each slab.
# TYPE node_slabinfo_pages_per_slab gauge
node_slabinfo_pages_per_slab{slab="dmaengine-unmap-128"} 8
node_slabinfo_pages_per_slab{slab="kmalloc-8192"} 8
node_slabinfo_pages_per_slab{slab="kmem_cache"} 2
node_slabinfo_pages_per_slab{slab="tw_sock_TCP"} 2
node_slabinfo_pages_per_slab{index="0",slab="mlx5_fs_ftes"} 8
node_slabinfo_pages_per_slab{index="1",slab="mlx5_fs_ftes"} 8
# HELP node_sockstat_FRAG6_inuse Number of FRAG6 sockets in state inuse.
# TYPE node_sockstat_FRAG6_inuse gauge
node_sockstat_FRAG6_inuse 0
Expand Down
2 changes: 2 additions & 0 deletions collector/fixtures/proc/slabinfo
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,7 @@ slabinfo - version: 2.1
# name <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> : tunables <limit> <batchcount> <sharedfactor> : slabdata <active_slabs> <num_slabs> <sharedavail>
tw_sock_TCP 704 864 256 32 2 : tunables 0 0 0 : slabdata 27 27 0
dmaengine-unmap-128 1206 1320 1088 30 8 : tunables 0 0 0 : slabdata 44 44 0
mlx5_fs_ftes 44 44 736 44 8 : tunables 0 0 0 : slabdata 1 1 0
mlx5_fs_ftes 396 396 736 44 8 : tunables 0 0 0 : slabdata 9 9 0
kmalloc-8192 132 148 8192 4 8 : tunables 0 0 0 : slabdata 37 37 0
kmem_cache 320 320 256 32 2 : tunables 0 0 0 : slabdata 10 10 0
99 changes: 69 additions & 30 deletions collector/slabinfo_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package collector
import (
"fmt"
"log/slog"
"strconv"

"github.com/alecthomas/kingpin/v2"
"github.com/prometheus/client_golang/prometheus"
Expand All @@ -34,6 +35,7 @@ type slabinfoCollector struct {
logger *slog.Logger
subsystem string
labels []string
indexedLabels []string
slabNameFilter deviceFilter
}

Expand All @@ -51,6 +53,7 @@ func NewSlabinfoCollector(logger *slog.Logger) (Collector, error) {
fs: fs,
subsystem: "slabinfo",
labels: []string{"slab"},
indexedLabels: []string{"slab", "index"},
slabNameFilter: newDeviceFilter(*slabNameExclude, *slabNameInclude),
}, nil
}
Expand All @@ -61,71 +64,107 @@ func (c *slabinfoCollector) Update(ch chan<- prometheus.Metric) error {
return fmt.Errorf("couldn't get %s: %w", c.subsystem, err)
}

// /proc/slabinfo can list the same slab name more than once, for example one
// cache per device instance. The kernel permits this: the duplicate-name check
// in kmem_cache_sanity_check() only WARNs, and is compiled out unless
// CONFIG_DEBUG_VM is set. Labelling by name alone therefore produces duplicate
// label sets, which fails the entire scrape.
//
// Pass 1: count the entries sharing each name. The filter matches on name, so
// every entry with a given name is kept or dropped together and counting ahead
// of it gives the same answer.
counts := make(map[string]int, len(slabinfo.Slabs))
for _, slab := range slabinfo.Slabs {
counts[slab.Name]++
}

// Pass 2: emit. Only names that actually collide are disambiguated, so a host
// whose slab names are all unique keeps exactly the series it had before. Where
// a name does repeat, each entry is given its position among the entries
// sharing that name, so every cache keeps its own series and its own geometry.
// The ordinal reflects the order of /proc/slabinfo and is not a stable
// identity: if a cache is created or destroyed, subsequent entries shift.
seen := make(map[string]int, len(counts))
for _, slab := range slabinfo.Slabs {
if c.slabNameFilter.ignored(slab.Name) {
continue
}
ch <- c.activeObjects(slab.Name, slab.ObjActive)
ch <- c.objects(slab.Name, slab.ObjNum)
ch <- c.objectSizeBytes(slab.Name, slab.ObjSize)
ch <- c.objectsPerSlab(slab.Name, slab.ObjPerSlab)
ch <- c.pagesPerSlab(slab.Name, slab.PagesPerSlab)
labelValues := []string{slab.Name}
if counts[slab.Name] > 1 {
labelValues = append(labelValues, strconv.Itoa(seen[slab.Name]))
seen[slab.Name]++
}
ch <- c.activeObjects(labelValues, slab.ObjActive)
ch <- c.objects(labelValues, slab.ObjNum)
ch <- c.objectSizeBytes(labelValues, slab.ObjSize)
ch <- c.objectsPerSlab(labelValues, slab.ObjPerSlab)
ch <- c.pagesPerSlab(labelValues, slab.PagesPerSlab)
}

return nil
}

func (c *slabinfoCollector) activeObjects(label string, val int64) prometheus.Metric {
desc := prometheus.NewDesc(
prometheus.BuildFQName(namespace, c.subsystem, "active_objects"),
// desc builds the descriptor for one slabinfo column. Entries whose name is
// unique are described by {slab}, the label set this collector has always used;
// only entries carrying an ordinal are described by {slab,index}.
func (c *slabinfoCollector) desc(name, help string, labelValues []string) *prometheus.Desc {
labels := c.labels
if len(labelValues) > len(c.labels) {
labels = c.indexedLabels
}

return prometheus.NewDesc(
prometheus.BuildFQName(namespace, c.subsystem, name),
help,
labels, nil)
}

func (c *slabinfoCollector) activeObjects(labelValues []string, val int64) prometheus.Metric {
desc := c.desc("active_objects",
"The number of objects that are currently active (i.e., in use).",
c.labels, nil)
labelValues)

return prometheus.MustNewConstMetric(
desc, prometheus.GaugeValue, float64(val), label,
desc, prometheus.GaugeValue, float64(val), labelValues...,
)
}

func (c *slabinfoCollector) objects(label string, val int64) prometheus.Metric {
desc := prometheus.NewDesc(
prometheus.BuildFQName(namespace, c.subsystem, "objects"),
func (c *slabinfoCollector) objects(labelValues []string, val int64) prometheus.Metric {
desc := c.desc("objects",
"The total number of allocated objects (i.e., objects that are both in use and not in use).",
c.labels, nil)
labelValues)

return prometheus.MustNewConstMetric(
desc, prometheus.GaugeValue, float64(val), label,
desc, prometheus.GaugeValue, float64(val), labelValues...,
)
}

func (c *slabinfoCollector) objectSizeBytes(label string, val int64) prometheus.Metric {
desc := prometheus.NewDesc(
prometheus.BuildFQName(namespace, c.subsystem, "object_size_bytes"),
func (c *slabinfoCollector) objectSizeBytes(labelValues []string, val int64) prometheus.Metric {
desc := c.desc("object_size_bytes",
"The size of objects in this slab, in bytes.",
c.labels, nil)
labelValues)

return prometheus.MustNewConstMetric(
desc, prometheus.GaugeValue, float64(val), label,
desc, prometheus.GaugeValue, float64(val), labelValues...,
)
}

func (c *slabinfoCollector) objectsPerSlab(label string, val int64) prometheus.Metric {
desc := prometheus.NewDesc(
prometheus.BuildFQName(namespace, c.subsystem, "objects_per_slab"),
func (c *slabinfoCollector) objectsPerSlab(labelValues []string, val int64) prometheus.Metric {
desc := c.desc("objects_per_slab",
"The number of objects stored in each slab.",
c.labels, nil)
labelValues)

return prometheus.MustNewConstMetric(
desc, prometheus.GaugeValue, float64(val), label,
desc, prometheus.GaugeValue, float64(val), labelValues...,
)
}

func (c *slabinfoCollector) pagesPerSlab(label string, val int64) prometheus.Metric {
desc := prometheus.NewDesc(
prometheus.BuildFQName(namespace, c.subsystem, "pages_per_slab"),
func (c *slabinfoCollector) pagesPerSlab(labelValues []string, val int64) prometheus.Metric {
desc := c.desc("pages_per_slab",
"The number of pages allocated for each slab.",
c.labels, nil)
labelValues)

return prometheus.MustNewConstMetric(
desc, prometheus.GaugeValue, float64(val), label,
desc, prometheus.GaugeValue, float64(val), labelValues...,
)
}
Loading
Loading