From 9ed338ec2e2fc42ac0d1e24701d673e50b0fb612 Mon Sep 17 00:00:00 2001 From: Pavel Litvyak Date: Tue, 20 Jan 2026 18:41:06 +0200 Subject: [PATCH 1/4] feature(inputs/mem): add extended linux fields --- go.mod | 12 +- go.sum | 24 +-- plugins/inputs/mem/README.md | 15 +- plugins/inputs/mem/mem.go | 13 ++ plugins/inputs/mem/mem_linux.go | 26 +++ plugins/inputs/mem/mem_linux_test.go | 152 +++++++++++++++++ plugins/inputs/mem/mem_other.go | 14 ++ plugins/inputs/mem/mem_test.go | 239 +++++++++++++++++++-------- 8 files changed, 399 insertions(+), 96 deletions(-) create mode 100644 plugins/inputs/mem/mem_linux.go create mode 100644 plugins/inputs/mem/mem_linux_test.go create mode 100644 plugins/inputs/mem/mem_other.go diff --git a/go.mod b/go.mod index 5f69c0fec9268..819218ac1871e 100644 --- a/go.mod +++ b/go.mod @@ -189,7 +189,7 @@ require ( github.com/santhosh-tekuri/jsonschema/v5 v5.3.1 github.com/seancfoley/ipaddress-go v1.7.1 github.com/sensu/sensu-go/api/core/v2 v2.16.0 - github.com/shirou/gopsutil/v4 v4.25.5 + github.com/shirou/gopsutil/v4 v4.25.12 github.com/showwin/speedtest-go v1.7.10 github.com/signalfx/golib/v3 v3.3.54 github.com/sijms/go-ora/v2 v2.9.0 @@ -198,7 +198,7 @@ require ( github.com/snowflakedb/gosnowflake v1.14.1 github.com/srebhan/cborquery v1.0.4 github.com/srebhan/protobufquery v1.0.4 - github.com/stretchr/testify v1.10.0 + github.com/stretchr/testify v1.11.1 github.com/tbrandon/mbserver v0.0.0-20170611213546-993e1772cc62 github.com/tdrn-org/go-hue v0.3.0 github.com/testcontainers/testcontainers-go v0.37.0 @@ -231,7 +231,7 @@ require ( golang.org/x/net v0.41.0 golang.org/x/oauth2 v0.30.0 golang.org/x/sync v0.15.0 - golang.org/x/sys v0.33.0 + golang.org/x/sys v0.38.0 golang.org/x/term v0.32.0 golang.org/x/text v0.26.0 golang.zx2c4.com/wireguard/wgctrl v0.0.0-20211230205640-daad0b7ba671 @@ -362,7 +362,7 @@ require ( github.com/eapache/go-resiliency v1.7.0 // indirect github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 // indirect github.com/eapache/queue v1.1.0 // indirect - github.com/ebitengine/purego v0.8.4 // indirect + github.com/ebitengine/purego v0.9.1 // indirect github.com/echlebek/timeproxy v1.0.0 // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect @@ -524,8 +524,8 @@ require ( github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.1 // indirect github.com/tidwall/tinylru v1.2.1 // indirect - github.com/tklauser/go-sysconf v0.3.13 // indirect - github.com/tklauser/numcpus v0.7.0 // indirect + github.com/tklauser/go-sysconf v0.3.16 // indirect + github.com/tklauser/numcpus v0.11.0 // indirect github.com/twmb/murmur3 v1.1.7 // indirect github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect github.com/uber/jaeger-lib v2.4.1+incompatible // indirect diff --git a/go.sum b/go.sum index bf0e755b0b4eb..4cc57c0445858 100644 --- a/go.sum +++ b/go.sum @@ -1185,8 +1185,8 @@ github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3 h1:Oy0F4A github.com/eapache/go-xerial-snappy v0.0.0-20230731223053-c322873962e3/go.mod h1:YvSRo5mw33fLEx1+DlK6L2VV43tJt5Eyel9n9XBcR+0= github.com/eapache/queue v1.1.0 h1:YOEu7KNc61ntiQlcEeUIoDTJ2o8mQznoNvUhiigpIqc= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= -github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= -github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A= +github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/echlebek/crock v1.0.1 h1:KbzamClMIfVIkkjq/GTXf+N16KylYBpiaTitO3f1ujg= github.com/echlebek/crock v1.0.1/go.mod h1:/kvwHRX3ZXHj/kHWJkjXDmzzRow54EJuHtQ/PapL/HI= github.com/echlebek/timeproxy v1.0.0 h1:V41/v8tmmMDNMA2GrBPI45nlXb3F7+OY+nJz1BqKsCk= @@ -2310,8 +2310,8 @@ github.com/sensu/sensu-go/api/core/v2 v2.16.0/go.mod h1:MjM7+MCGEyTAgaZ589SiGHwY github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shirou/gopsutil v3.21.11+incompatible h1:+1+c1VGhc88SSonWP6foOcLhvnKlUeu/erjjvaPEYiI= github.com/shirou/gopsutil v3.21.11+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= -github.com/shirou/gopsutil/v4 v4.25.5 h1:rtd9piuSMGeU8g1RMXjZs9y9luK5BwtnG7dZaQUJAsc= -github.com/shirou/gopsutil/v4 v4.25.5/go.mod h1:PfybzyydfZcN+JMMjkF6Zb8Mq1A/VcogFFg7hj50W9c= +github.com/shirou/gopsutil/v4 v4.25.12 h1:e7PvW/0RmJ8p8vPGJH4jvNkOyLmbkXgXW4m6ZPic6CY= +github.com/shirou/gopsutil/v4 v4.25.12/go.mod h1:EivAfP5x2EhLp2ovdpKSozecVXn1TmuG7SMzs/Wh4PU= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= @@ -2408,8 +2408,8 @@ github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= -github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/t3rm1n4l/go-mega v0.0.0-20241213150454-ec0027fb0002 h1:jevGbwKzMmHLgHAaDaMJLQX3jpXUWjUvnsrPeMgkM7o= github.com/t3rm1n4l/go-mega v0.0.0-20241213150454-ec0027fb0002/go.mod h1:0Mv/XWQoRWF7d7jkc4DufsAJQg8xyZ5NtCkY59wECQY= @@ -2453,10 +2453,10 @@ github.com/tj/go-buffer v1.1.0/go.mod h1:iyiJpfFcR2B9sXu7KvjbT9fpM4mOelRSDTbntVj github.com/tj/go-elastic v0.0.0-20171221160941-36157cbbebc2/go.mod h1:WjeM0Oo1eNAjXGDx2yma7uG2XoyRZTq1uv3M/o7imD0= github.com/tj/go-kinesis v0.0.0-20171128231115-08b17f58cb1b/go.mod h1:/yhzCV0xPfx6jb1bBgRFjl5lytqVqZXEaeqWP8lTEao= github.com/tj/go-spin v1.1.0/go.mod h1:Mg1mzmePZm4dva8Qz60H2lHwmJ2loum4VIrLgVnKwh4= -github.com/tklauser/go-sysconf v0.3.13 h1:GBUpcahXSpR2xN01jhkNAbTLRk2Yzgggk8IM08lq3r4= -github.com/tklauser/go-sysconf v0.3.13/go.mod h1:zwleP4Q4OehZHGn4CYZDipCgg9usW5IJePewFCGVEa0= -github.com/tklauser/numcpus v0.7.0 h1:yjuerZP127QG9m5Zh/mSO4wqurYil27tHrqwRoRjpr4= -github.com/tklauser/numcpus v0.7.0/go.mod h1:bb6dMVcj8A42tSE7i32fsIUCbQNllK5iDguyOZRUzAY= +github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA= +github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI= +github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw= +github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/twmb/murmur3 v1.1.7 h1:ULWBiM04n/XoN3YMSJ6Z2pHDFLf+MeIVQU71ZPrvbWg= @@ -3027,8 +3027,8 @@ golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= -golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= diff --git a/plugins/inputs/mem/README.md b/plugins/inputs/mem/README.md index aeb3f56570f11..0a5f62e1e225d 100644 --- a/plugins/inputs/mem/README.md +++ b/plugins/inputs/mem/README.md @@ -14,10 +14,9 @@ This plugin collects metrics about the system memory. ## Global configuration options -In addition to the plugin-specific configuration settings, plugins support -additional global and plugin configuration settings. These settings are used to -modify metrics, tags, and field or create aliases and configure ordering, etc. -See the [CONFIGURATION.md][CONFIGURATION.md] for more details. +Plugins support additional global and plugin configuration settings for tasks +such as modifying metrics, tags, and fields, creating aliases, and configuring +plugin ordering. See [CONFIGURATION.md][CONFIGURATION.md] for more details. [CONFIGURATION.md]: ../../../docs/CONFIGURATION.md#plugins @@ -36,6 +35,8 @@ Available fields are dependent on platform. - mem - fields: - active (integer, Darwin, FreeBSD, Linux, OpenBSD) + - active_anon (integer, Linux) + - active_file (integer, Linux) - available (integer) - available_percent (float) - buffered (integer, FreeBSD, Linux) @@ -50,11 +51,14 @@ Available fields are dependent on platform. - huge_page_size (integer, Linux) - huge_pages_total (integer, Linux) - inactive (integer, Darwin, FreeBSD, Linux, OpenBSD) + - inactive_anon (integer, Linux) + - inactive_file (integer, Linux) - laundry (integer, FreeBSD) - low_free (integer, Linux) - low_total (integer, Linux) - mapped (integer, Linux) - page_tables (integer, Linux) + - percpu (integer, Linux) - shared (integer, Linux) - slab (integer, Linux) - sreclaimable (integer, Linux) @@ -63,6 +67,7 @@ Available fields are dependent on platform. - swap_free (integer, Linux) - swap_total (integer, Linux) - total (integer) + - unevictable (integer, Linux) - used (integer) - used_percent (float) - vmalloc_chunk (integer, Linux) @@ -75,5 +80,5 @@ Available fields are dependent on platform. ## Example Output ```text -mem active=9299595264i,available=16818249728i,available_percent=80.41654254645131,buffered=2383761408i,cached=13316689920i,commit_limit=14751920128i,committed_as=11781156864i,dirty=122880i,free=1877688320i,high_free=0i,high_total=0i,huge_page_size=2097152i,huge_pages_free=0i,huge_pages_total=0i,inactive=7549939712i,low_free=0i,low_total=0i,mapped=416763904i,page_tables=19787776i,shared=670679040i,slab=2081071104i,sreclaimable=1923395584i,sunreclaim=157675520i,swap_cached=1302528i,swap_free=4286128128i,swap_total=4294963200i,total=20913917952i,used=3335778304i,used_percent=15.95004011996231,vmalloc_chunk=0i,vmalloc_total=35184372087808i,vmalloc_used=0i,wired=0i,write_back=0i,write_back_tmp=0i 1574712869000000000 +mem active=9299595264i,active_anon=5765169152i,active_file=3534426112i,available=16818249728i,available_percent=80.41654254645131,buffered=2383761408i,cached=13316689920i,commit_limit=14751920128i,committed_as=11781156864i,dirty=122880i,free=1877688320i,high_free=0i,high_total=0i,huge_page_size=2097152i,huge_pages_free=0i,huge_pages_total=0i,inactive=7549939712i,inactive_anon=1081245696i,inactive_file=6468694016i,low_free=0i,low_total=0i,mapped=416763904i,page_tables=19787776i,percpu=5765120i,shared=670679040i,slab=2081071104i,sreclaimable=1923395584i,sunreclaim=157675520i,swap_cached=1302528i,swap_free=4286128128i,swap_total=4294963200i,total=20913917952i,unevictable=143360i,used=3335778304i,used_percent=15.95004011996231,vmalloc_chunk=0i,vmalloc_total=35184372087808i,vmalloc_used=0i,write_back=0i,write_back_tmp=0i 1574712869000000000 ``` diff --git a/plugins/inputs/mem/mem.go b/plugins/inputs/mem/mem.go index 561f2dec382c9..e47ccecefdfcb 100644 --- a/plugins/inputs/mem/mem.go +++ b/plugins/inputs/mem/mem.go @@ -14,9 +14,15 @@ import ( //go:embed sample.conf var sampleConfig string +// extendedMemoryStats provides extended memory statistics for a platform. +type extendedMemoryStats interface { + addFields(fields map[string]interface{}) error +} + type Mem struct { ps psutil.PS platform string + exStats extendedMemoryStats } func (*Mem) SampleConfig() string { @@ -25,6 +31,9 @@ func (*Mem) SampleConfig() string { func (ms *Mem) Init() error { ms.platform = runtime.GOOS + if ms.exStats == nil { + ms.exStats = newExtendedMemoryStats() + } return nil } @@ -92,6 +101,10 @@ func (ms *Mem) Gather(acc telegraf.Accumulator) error { fields["vmalloc_used"] = vm.VmallocUsed fields["write_back_tmp"] = vm.WriteBackTmp fields["write_back"] = vm.WriteBack + + if err := ms.exStats.addFields(fields); err != nil { + acc.AddError(fmt.Errorf("error getting extended virtual memory info: %w", err)) + } } acc.AddGauge("mem", fields, nil) diff --git a/plugins/inputs/mem/mem_linux.go b/plugins/inputs/mem/mem_linux.go new file mode 100644 index 0000000000000..1a53217d01efa --- /dev/null +++ b/plugins/inputs/mem/mem_linux.go @@ -0,0 +1,26 @@ +package mem + +import ( + "github.com/shirou/gopsutil/v4/mem" +) + +type linuxExtendedMemoryStats struct{} + +func newExtendedMemoryStats() extendedMemoryStats { + return &linuxExtendedMemoryStats{} +} + +// addFields adds extended virtual memory statistics from /proc/meminfo to the fields map. +func (l *linuxExtendedMemoryStats) addFields(fields map[string]interface{}) error { + exVM, err := mem.NewExLinux().VirtualMemory() + if err != nil { + return err + } + fields["active_file"] = exVM.ActiveFile + fields["inactive_file"] = exVM.InactiveFile + fields["active_anon"] = exVM.ActiveAnon + fields["inactive_anon"] = exVM.InactiveAnon + fields["unevictable"] = exVM.Unevictable + fields["percpu"] = exVM.Percpu + return nil +} diff --git a/plugins/inputs/mem/mem_linux_test.go b/plugins/inputs/mem/mem_linux_test.go new file mode 100644 index 0000000000000..2d60bd98e8c1d --- /dev/null +++ b/plugins/inputs/mem/mem_linux_test.go @@ -0,0 +1,152 @@ +//go:build linux + +package mem + +import ( + "testing" + "time" + + "github.com/shirou/gopsutil/v4/mem" + "github.com/stretchr/testify/require" + + "github.com/influxdata/telegraf" + "github.com/influxdata/telegraf/plugins/common/psutil" + "github.com/influxdata/telegraf/testutil" +) + +// mockExtendedMemoryStats implements extendedMemoryStats for testing. +type mockExtendedMemoryStats struct { + addFieldsFunc func(fields map[string]interface{}) error +} + +func (m *mockExtendedMemoryStats) addFields(fields map[string]interface{}) error { + if m.addFieldsFunc != nil { + return m.addFieldsFunc(fields) + } + return nil +} + +func TestMemStatsLinux(t *testing.T) { + var mps psutil.MockPS + defer mps.AssertExpectations(t) + var acc testutil.Accumulator + + vms := &mem.VirtualMemoryStat{ + Total: 16589934592, + Available: 8294967296, + Used: 8294967296, + Free: 2147483648, + Active: 4294967296, + Inactive: 2147483648, + Buffers: 536870912, + Cached: 2147483648, + Slab: 268435456, + Shared: 134217728, + Sreclaimable: 134217728, + Sunreclaim: 67108864, + CommitLimit: 8589934592, + CommittedAS: 4294967296, + Dirty: 1048576, + HighFree: 0, + HighTotal: 0, + HugePageSize: 2097152, + HugePagesFree: 0, + HugePagesTotal: 0, + LowFree: 2147483648, + LowTotal: 16589934592, + Mapped: 536870912, + PageTables: 33554432, + SwapCached: 0, + SwapFree: 8589934592, + SwapTotal: 8589934592, + VmallocChunk: 0, + VmallocTotal: 35184372088832, + VmallocUsed: 67108864, + WriteBack: 0, + WriteBackTmp: 0, + } + + mps.On("VMStat").Return(vms, nil) + + // Inject mock for extended memory stats + mockExStats := &mockExtendedMemoryStats{ + addFieldsFunc: func(fields map[string]interface{}) error { + fields["active_file"] = uint64(1073741824) + fields["inactive_file"] = uint64(2147483648) + fields["active_anon"] = uint64(536870912) + fields["inactive_anon"] = uint64(268435456) + fields["unevictable"] = uint64(134217728) + fields["percpu"] = uint64(67108864) + return nil + }, + } + + plugin := &Mem{ + ps: &mps, + exStats: mockExStats, + } + + err := plugin.Init() + require.NoError(t, err) + + plugin.platform = "linux" + + err = plugin.Gather(&acc) + require.NoError(t, err) + + expected := []telegraf.Metric{ + testutil.MustMetric( + "mem", + map[string]string{}, + map[string]interface{}{ + // Common fields + "total": uint64(16589934592), + "available": uint64(8294967296), + "used": uint64(8294967296), + "used_percent": float64(8294967296) / float64(16589934592) * 100, + "available_percent": float64(8294967296) / float64(16589934592) * 100, + // Linux-specific fields from VirtualMemoryStat + "free": uint64(2147483648), + "buffered": uint64(536870912), + "cached": uint64(2147483648), + "active": uint64(4294967296), + "inactive": uint64(2147483648), + "slab": uint64(268435456), + "shared": uint64(134217728), + "sreclaimable": uint64(134217728), + "sunreclaim": uint64(67108864), + "commit_limit": uint64(8589934592), + "committed_as": uint64(4294967296), + "dirty": uint64(1048576), + "high_free": uint64(0), + "high_total": uint64(0), + "huge_page_size": uint64(2097152), + "huge_pages_free": uint64(0), + "huge_pages_total": uint64(0), + "low_free": uint64(2147483648), + "low_total": uint64(16589934592), + "mapped": uint64(536870912), + "page_tables": uint64(33554432), + "swap_cached": uint64(0), + "swap_free": uint64(8589934592), + "swap_total": uint64(8589934592), + "vmalloc_chunk": uint64(0), + "vmalloc_total": uint64(35184372088832), + "vmalloc_used": uint64(67108864), + "write_back": uint64(0), + "write_back_tmp": uint64(0), + // Extended fields from ExVirtualMemory (mocked) + "active_file": uint64(1073741824), + "inactive_file": uint64(2147483648), + "active_anon": uint64(536870912), + "inactive_anon": uint64(268435456), + "unevictable": uint64(134217728), + "percpu": uint64(67108864), + }, + time.Unix(0, 0), + telegraf.Gauge, + ), + } + + testutil.RequireMetricsEqual(t, expected, acc.GetTelegrafMetrics(), testutil.IgnoreTime()) +} diff --git a/plugins/inputs/mem/mem_other.go b/plugins/inputs/mem/mem_other.go new file mode 100644 index 0000000000000..f40f360f757b0 --- /dev/null +++ b/plugins/inputs/mem/mem_other.go @@ -0,0 +1,14 @@ +//go:build !linux + +package mem + +type noopExtendedMemoryStats struct{} + +func newExtendedMemoryStats() extendedMemoryStats { + return &noopExtendedMemoryStats{} +} + +// addFields is a no-op on non-Linux platforms as extended VM stats are not available. +func (n *noopExtendedMemoryStats) addFields(fields map[string]interface{}) error { + return nil +} diff --git a/plugins/inputs/mem/mem_test.go b/plugins/inputs/mem/mem_test.go index 313a43ce8d5f5..5433303120a62 100644 --- a/plugins/inputs/mem/mem_test.go +++ b/plugins/inputs/mem/mem_test.go @@ -12,58 +12,176 @@ import ( "github.com/influxdata/telegraf/testutil" ) -func TestMemStats(t *testing.T) { +func TestMemStatsBasic(t *testing.T) { var mps psutil.MockPS - var err error defer mps.AssertExpectations(t) var acc testutil.Accumulator vms := &mem.VirtualMemoryStat{ - Total: 12400, - Available: 7600, - Used: 5000, - Free: 1235, - Active: 8134, - Inactive: 1124, - Slab: 1234, - Wired: 134, - // Buffers: 771, - // Cached: 4312, - // Shared: 2142, - CommitLimit: 1, - CommittedAS: 118680, - Dirty: 4, - HighFree: 0, - HighTotal: 0, - HugePageSize: 4096, - HugePagesFree: 0, - HugePagesTotal: 0, - LowFree: 69936, - LowTotal: 255908, - Mapped: 42236, - PageTables: 1236, - Shared: 0, - Sreclaimable: 1923022848, - Sunreclaim: 157728768, - SwapCached: 0, - SwapFree: 524280, - SwapTotal: 524280, - VmallocChunk: 3872908, - VmallocTotal: 3874808, - VmallocUsed: 1416, - WriteBack: 0, - WriteBackTmp: 0, + Total: 8589934592, + Available: 4294967296, + Used: 4294967296, } mps.On("VMStat").Return(vms, nil) plugin := &Mem{ps: &mps} - err = plugin.Init() + err := plugin.Init() require.NoError(t, err) - plugin.platform = "linux" + // Use unknown platform to get only basic fields + plugin.platform = "unknown" + err = plugin.Gather(&acc) require.NoError(t, err) + + expected := []telegraf.Metric{ + testutil.MustMetric( + "mem", + map[string]string{}, + map[string]interface{}{ + "total": uint64(8589934592), + "available": uint64(4294967296), + "used": uint64(4294967296), + "used_percent": float64(4294967296) / float64(8589934592) * 100, + "available_percent": float64(4294967296) / float64(8589934592) * 100, + }, + time.Unix(0, 0), + telegraf.Gauge, + ), + } + + testutil.RequireMetricsEqual(t, expected, acc.GetTelegrafMetrics(), testutil.IgnoreTime()) +} + +func TestMemStatsDarwin(t *testing.T) { + var mps psutil.MockPS + defer mps.AssertExpectations(t) + var acc testutil.Accumulator + + vms := &mem.VirtualMemoryStat{ + Total: 17179869184, + Available: 6653214720, + Used: 10526654464, + Free: 3221225472, + Active: 4294967296, + Inactive: 2147483648, + Wired: 3758096384, + } + + mps.On("VMStat").Return(vms, nil) + plugin := &Mem{ps: &mps} + + err := plugin.Init() + require.NoError(t, err) + + plugin.platform = "darwin" + + err = plugin.Gather(&acc) + require.NoError(t, err) + + expected := []telegraf.Metric{ + testutil.MustMetric( + "mem", + map[string]string{}, + map[string]interface{}{ + "total": uint64(17179869184), + "available": uint64(6653214720), + "used": uint64(10526654464), + "used_percent": float64(10526654464) / float64(17179869184) * 100, + "available_percent": float64(6653214720) / float64(17179869184) * 100, + "free": uint64(3221225472), + "active": uint64(4294967296), + "inactive": uint64(2147483648), + "wired": uint64(3758096384), + }, + time.Unix(0, 0), + telegraf.Gauge, + ), + } + + testutil.RequireMetricsEqual(t, expected, acc.GetTelegrafMetrics(), testutil.IgnoreTime()) +} + +func TestMemStatsFreeBSD(t *testing.T) { + var mps psutil.MockPS + defer mps.AssertExpectations(t) + var acc testutil.Accumulator + + vms := &mem.VirtualMemoryStat{ + Total: 8589934592, + Available: 4294967296, + Used: 4294967296, + Free: 2147483648, + Active: 1073741824, + Inactive: 536870912, + Buffers: 268435456, + Cached: 1073741824, + Wired: 536870912, + Laundry: 134217728, + } + + mps.On("VMStat").Return(vms, nil) + plugin := &Mem{ps: &mps} + + err := plugin.Init() + require.NoError(t, err) + + plugin.platform = "freebsd" + + err = plugin.Gather(&acc) + require.NoError(t, err) + + expected := []telegraf.Metric{ + testutil.MustMetric( + "mem", + map[string]string{}, + map[string]interface{}{ + "total": uint64(8589934592), + "available": uint64(4294967296), + "used": uint64(4294967296), + "used_percent": float64(4294967296) / float64(8589934592) * 100, + "available_percent": float64(4294967296) / float64(8589934592) * 100, + "free": uint64(2147483648), + "active": uint64(1073741824), + "inactive": uint64(536870912), + "buffered": uint64(268435456), + "cached": uint64(1073741824), + "wired": uint64(536870912), + "laundry": uint64(134217728), + }, + time.Unix(0, 0), + telegraf.Gauge, + ), + } + + testutil.RequireMetricsEqual(t, expected, acc.GetTelegrafMetrics(), testutil.IgnoreTime()) +} + +func TestMemStatsOpenBSD(t *testing.T) { + var mps psutil.MockPS + defer mps.AssertExpectations(t) + var acc testutil.Accumulator + + vms := &mem.VirtualMemoryStat{ + Total: 4294967296, + Available: 2147483648, + Used: 2147483648, + Free: 1073741824, + Active: 536870912, + Inactive: 268435456, + Cached: 536870912, + Wired: 268435456, + } + + mps.On("VMStat").Return(vms, nil) + plugin := &Mem{ps: &mps} + + err := plugin.Init() + require.NoError(t, err) + + plugin.platform = "openbsd" + err = plugin.Gather(&acc) require.NoError(t, err) @@ -72,41 +190,16 @@ func TestMemStats(t *testing.T) { "mem", map[string]string{}, map[string]interface{}{ - "total": uint64(12400), - "available": uint64(7600), - "used": uint64(5000), - "available_percent": float64(7600) / float64(12400) * 100, - "used_percent": float64(5000) / float64(12400) * 100, - "free": uint64(1235), - "cached": uint64(0), - "buffered": uint64(0), - "active": uint64(8134), - "inactive": uint64(1124), - // "wired": uint64(134), - "slab": uint64(1234), - "commit_limit": uint64(1), - "committed_as": uint64(118680), - "dirty": uint64(4), - "high_free": uint64(0), - "high_total": uint64(0), - "huge_page_size": uint64(4096), - "huge_pages_free": uint64(0), - "huge_pages_total": uint64(0), - "low_free": uint64(69936), - "low_total": uint64(255908), - "mapped": uint64(42236), - "page_tables": uint64(1236), - "shared": uint64(0), - "sreclaimable": uint64(1923022848), - "sunreclaim": uint64(157728768), - "swap_cached": uint64(0), - "swap_free": uint64(524280), - "swap_total": uint64(524280), - "vmalloc_chunk": uint64(3872908), - "vmalloc_total": uint64(3874808), - "vmalloc_used": uint64(1416), - "write_back": uint64(0), - "write_back_tmp": uint64(0), + "total": uint64(4294967296), + "available": uint64(2147483648), + "used": uint64(2147483648), + "used_percent": float64(2147483648) / float64(4294967296) * 100, + "available_percent": float64(2147483648) / float64(4294967296) * 100, + "free": uint64(1073741824), + "active": uint64(536870912), + "inactive": uint64(268435456), + "cached": uint64(536870912), + "wired": uint64(268435456), }, time.Unix(0, 0), telegraf.Gauge, From 196c19a799e66ca3291d984f5da1f75c71ded0e1 Mon Sep 17 00:00:00 2001 From: Pavel Litvyak Date: Fri, 23 Jan 2026 16:09:27 +0200 Subject: [PATCH 2/4] refactor(mem): replace addFields with getFields for extended memory stats --- plugins/inputs/mem/mem.go | 8 ++++++-- plugins/inputs/mem/mem_linux.go | 21 +++++++++++---------- plugins/inputs/mem/mem_linux_test.go | 27 ++++++++++++--------------- plugins/inputs/mem/mem_other.go | 6 +++--- 4 files changed, 32 insertions(+), 30 deletions(-) diff --git a/plugins/inputs/mem/mem.go b/plugins/inputs/mem/mem.go index e47ccecefdfcb..ce3584d0c430b 100644 --- a/plugins/inputs/mem/mem.go +++ b/plugins/inputs/mem/mem.go @@ -16,7 +16,7 @@ var sampleConfig string // extendedMemoryStats provides extended memory statistics for a platform. type extendedMemoryStats interface { - addFields(fields map[string]interface{}) error + getFields() (map[string]interface{}, error) } type Mem struct { @@ -102,9 +102,13 @@ func (ms *Mem) Gather(acc telegraf.Accumulator) error { fields["write_back_tmp"] = vm.WriteBackTmp fields["write_back"] = vm.WriteBack - if err := ms.exStats.addFields(fields); err != nil { + extFields, err := ms.exStats.getFields() + if err != nil { acc.AddError(fmt.Errorf("error getting extended virtual memory info: %w", err)) } + for k, v := range extFields { + fields[k] = v + } } acc.AddGauge("mem", fields, nil) diff --git a/plugins/inputs/mem/mem_linux.go b/plugins/inputs/mem/mem_linux.go index 1a53217d01efa..4dd338fe26854 100644 --- a/plugins/inputs/mem/mem_linux.go +++ b/plugins/inputs/mem/mem_linux.go @@ -10,17 +10,18 @@ func newExtendedMemoryStats() extendedMemoryStats { return &linuxExtendedMemoryStats{} } -// addFields adds extended virtual memory statistics from /proc/meminfo to the fields map. -func (l *linuxExtendedMemoryStats) addFields(fields map[string]interface{}) error { +// getFields returns extended virtual memory statistics from /proc/meminfo. +func (l *linuxExtendedMemoryStats) getFields() (map[string]interface{}, error) { exVM, err := mem.NewExLinux().VirtualMemory() if err != nil { - return err + return nil, err } - fields["active_file"] = exVM.ActiveFile - fields["inactive_file"] = exVM.InactiveFile - fields["active_anon"] = exVM.ActiveAnon - fields["inactive_anon"] = exVM.InactiveAnon - fields["unevictable"] = exVM.Unevictable - fields["percpu"] = exVM.Percpu - return nil + return map[string]interface{}{ + "active_file": exVM.ActiveFile, + "inactive_file": exVM.InactiveFile, + "active_anon": exVM.ActiveAnon, + "inactive_anon": exVM.InactiveAnon, + "unevictable": exVM.Unevictable, + "percpu": exVM.Percpu, + }, nil } diff --git a/plugins/inputs/mem/mem_linux_test.go b/plugins/inputs/mem/mem_linux_test.go index 2d60bd98e8c1d..5be96bccdbc33 100644 --- a/plugins/inputs/mem/mem_linux_test.go +++ b/plugins/inputs/mem/mem_linux_test.go @@ -16,14 +16,12 @@ import ( // mockExtendedMemoryStats implements extendedMemoryStats for testing. type mockExtendedMemoryStats struct { - addFieldsFunc func(fields map[string]interface{}) error + fields map[string]interface{} + err error } -func (m *mockExtendedMemoryStats) addFields(fields map[string]interface{}) error { - if m.addFieldsFunc != nil { - return m.addFieldsFunc(fields) - } - return nil +func (m *mockExtendedMemoryStats) getFields() (map[string]interface{}, error) { + return m.fields, m.err } func TestMemStatsLinux(t *testing.T) { @@ -70,14 +68,13 @@ func TestMemStatsLinux(t *testing.T) { // Inject mock for extended memory stats mockExStats := &mockExtendedMemoryStats{ - addFieldsFunc: func(fields map[string]interface{}) error { - fields["active_file"] = uint64(1073741824) - fields["inactive_file"] = uint64(2147483648) - fields["active_anon"] = uint64(536870912) - fields["inactive_anon"] = uint64(268435456) - fields["unevictable"] = uint64(134217728) - fields["percpu"] = uint64(67108864) - return nil + fields: map[string]interface{}{ + "active_file": uint64(1073741824), + "inactive_file": uint64(2147483648), + "active_anon": uint64(536870912), + "inactive_anon": uint64(268435456), + "unevictable": uint64(134217728), + "percpu": uint64(67108864), }, } @@ -139,7 +136,7 @@ func TestMemStatsLinux(t *testing.T) { "active_file": uint64(1073741824), "inactive_file": uint64(2147483648), "active_anon": uint64(536870912), - "inactive_anon": uint64(268435456), + "inactive_anon": uint64(268435457), "unevictable": uint64(134217728), "percpu": uint64(67108864), }, diff --git a/plugins/inputs/mem/mem_other.go b/plugins/inputs/mem/mem_other.go index f40f360f757b0..210dbe25e7091 100644 --- a/plugins/inputs/mem/mem_other.go +++ b/plugins/inputs/mem/mem_other.go @@ -8,7 +8,7 @@ func newExtendedMemoryStats() extendedMemoryStats { return &noopExtendedMemoryStats{} } -// addFields is a no-op on non-Linux platforms as extended VM stats are not available. -func (n *noopExtendedMemoryStats) addFields(fields map[string]interface{}) error { - return nil +// getFields returns nil on non-Linux platforms as extended VM stats are not available. +func (n *noopExtendedMemoryStats) getFields() (map[string]interface{}, error) { + return nil, nil } From f0ce153c75890df2ab97077d7780511c7648b0ad Mon Sep 17 00:00:00 2001 From: Pavel Litvyak Date: Fri, 23 Jan 2026 16:16:06 +0200 Subject: [PATCH 3/4] fix(mem): fix test values --- plugins/inputs/mem/mem_linux_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/inputs/mem/mem_linux_test.go b/plugins/inputs/mem/mem_linux_test.go index 5be96bccdbc33..35d83e12ce776 100644 --- a/plugins/inputs/mem/mem_linux_test.go +++ b/plugins/inputs/mem/mem_linux_test.go @@ -136,7 +136,7 @@ func TestMemStatsLinux(t *testing.T) { "active_file": uint64(1073741824), "inactive_file": uint64(2147483648), "active_anon": uint64(536870912), - "inactive_anon": uint64(268435457), + "inactive_anon": uint64(268435456), "unevictable": uint64(134217728), "percpu": uint64(67108864), }, From c3342e71c9540ddeb5bffb2af03da92b0d41414d Mon Sep 17 00:00:00 2001 From: Pavel Litvyak Date: Wed, 28 Jan 2026 19:47:54 +0200 Subject: [PATCH 4/4] fix(mem): align with golangci-lint --- plugins/inputs/mem/mem_linux.go | 2 +- plugins/inputs/mem/mem_other.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/inputs/mem/mem_linux.go b/plugins/inputs/mem/mem_linux.go index 4dd338fe26854..ac2a1ee3cebdc 100644 --- a/plugins/inputs/mem/mem_linux.go +++ b/plugins/inputs/mem/mem_linux.go @@ -11,7 +11,7 @@ func newExtendedMemoryStats() extendedMemoryStats { } // getFields returns extended virtual memory statistics from /proc/meminfo. -func (l *linuxExtendedMemoryStats) getFields() (map[string]interface{}, error) { +func (*linuxExtendedMemoryStats) getFields() (map[string]interface{}, error) { exVM, err := mem.NewExLinux().VirtualMemory() if err != nil { return nil, err diff --git a/plugins/inputs/mem/mem_other.go b/plugins/inputs/mem/mem_other.go index 210dbe25e7091..16f32c420c33f 100644 --- a/plugins/inputs/mem/mem_other.go +++ b/plugins/inputs/mem/mem_other.go @@ -9,6 +9,6 @@ func newExtendedMemoryStats() extendedMemoryStats { } // getFields returns nil on non-Linux platforms as extended VM stats are not available. -func (n *noopExtendedMemoryStats) getFields() (map[string]interface{}, error) { +func (*noopExtendedMemoryStats) getFields() (map[string]interface{}, error) { return nil, nil }