From 72849ee5f00c6ba5ae005a5fcc0f02229b2526e7 Mon Sep 17 00:00:00 2001 From: "zhiyuan.zhou" Date: Wed, 23 Apr 2025 19:41:38 +0800 Subject: [PATCH] Supports collection of process shared memory * Parse process memory usage from /proc/[pid]/statm into ProcStatm struct. * Add unit tests for proc_statm. * Add /proc/26231/statm test data to fixtures. Signed-off-by: zhiyuan.zhou --- proc_statm.go | 116 +++++++++++++++++++++++++++++++++++ proc_statm_test.go | 135 +++++++++++++++++++++++++++++++++++++++++ testdata/fixtures.ttar | 5 ++ 3 files changed, 256 insertions(+) create mode 100644 proc_statm.go create mode 100644 proc_statm_test.go diff --git a/proc_statm.go b/proc_statm.go new file mode 100644 index 00000000..0ebc2502 --- /dev/null +++ b/proc_statm.go @@ -0,0 +1,116 @@ +// Copyright 2025 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package procfs + +import ( + "os" + "strconv" + "strings" + + "github.com/prometheus/procfs/internal/util" +) + +// - https://man7.org/linux/man-pages/man5/proc_pid_statm.5.html + +// ProcStatm Provides memory usage information for a process, measured in memory pages. +// read from /proc/[pid]/statm. +type ProcStatm struct { + // The process ID. + PID int + // total program size (same as VmSize in status) + Size uint64 + // resident set size (same as VmRSS in status) + Resident uint64 + // number of resident shared pages (i.e., backed by a file) + Shared uint64 + // text (code) + Text uint64 + // library (unused since Linux 2.6; always 0) + Lib uint64 + // data + stack + Data uint64 + // dirty pages (unused since Linux 2.6; always 0) + Dt uint64 +} + +// NewStatm returns the current status information of the process. +// Deprecated: Use p.Statm() instead. +func (p Proc) NewStatm() (ProcStatm, error) { + return p.Statm() +} + +// Statm returns the current memory usage information of the process. +func (p Proc) Statm() (ProcStatm, error) { + data, err := util.ReadFileNoStat(p.path("statm")) + if err != nil { + return ProcStatm{}, err + } + + statmSlice, err := parseStatm(data) + if err != nil { + return ProcStatm{}, err + } + + procStatm := ProcStatm{ + PID: p.PID, + Size: statmSlice[0], + Resident: statmSlice[1], + Shared: statmSlice[2], + Text: statmSlice[3], + Lib: statmSlice[4], + Data: statmSlice[5], + Dt: statmSlice[6], + } + + return procStatm, nil +} + +// parseStatm return /proc/[pid]/statm data to uint64 slice. +func parseStatm(data []byte) ([]uint64, error) { + var statmSlice []uint64 + statmItems := strings.Fields(string(data)) + for i := 0; i < len(statmItems); i++ { + statmItem, err := strconv.ParseUint(statmItems[i], 10, 64) + if err != nil { + return nil, err + } + statmSlice = append(statmSlice, statmItem) + } + return statmSlice, nil +} + +// SizeBytes returns the process of total program size in bytes. +func (s ProcStatm) SizeBytes() uint64 { + return s.Size * uint64(os.Getpagesize()) +} + +// ResidentBytes returns the process of resident set size in bytes. +func (s ProcStatm) ResidentBytes() uint64 { + return s.Resident * uint64(os.Getpagesize()) +} + +// SHRBytes returns the process of share memory size in bytes. +func (s ProcStatm) SHRBytes() uint64 { + return s.Shared * uint64(os.Getpagesize()) +} + +// TextBytes returns the process of text (code) size in bytes. +func (s ProcStatm) TextBytes() uint64 { + return s.Text * uint64(os.Getpagesize()) +} + +// DataBytes returns the process of data + stack size in bytes. +func (s ProcStatm) DataBytes() uint64 { + return s.Data * uint64(os.Getpagesize()) +} diff --git a/proc_statm_test.go b/proc_statm_test.go new file mode 100644 index 00000000..8d2c651d --- /dev/null +++ b/proc_statm_test.go @@ -0,0 +1,135 @@ +// Copyright 2025 The Prometheus Authors +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package procfs + +import ( + "math" + "os" + "testing" +) + +func TestProcStatm(t *testing.T) { + statm, err := testProcStatm(26231) + if err != nil { + t.Fatal(err) + } + + for _, test := range []struct { + name string + want uint64 + have uint64 + }{ + {name: "Pid", want: 26231, have: uint64(statm.PID)}, + {name: "Size", want: 149919, have: statm.Size}, + {name: "Resident", want: 12547, have: statm.Resident}, + {name: "Shared", want: 18446744073709551615, have: statm.Shared}, + {name: "Text", want: 19864, have: statm.Text}, + {name: "Lib", want: 0, have: statm.Lib}, + {name: "Data", want: 14531, have: statm.Data}, + {name: "Dt", want: 0, have: statm.Dt}, + } { + if test.want != test.have { + t.Errorf("want %s %d, have %d", test.name, test.want, test.have) + } + } +} + +func TestProcStatmLimits(t *testing.T) { + statm, err := testProcStatm(26231) + if err != nil { + t.Fatal(err) + } + + // max values of stat int fields + for _, test := range []struct { + name string + want uint64 + have uint64 + }{ + {name: "number of resident shared pages in process", want: math.MaxUint64, have: statm.Shared}, + {name: "number of dirty pages in process", want: 0, have: statm.Dt}, + } { + if test.want != test.have { + t.Errorf("want %s %d, have %d", test.name, test.want, test.have) + } + } +} + +func TestSizeBytes(t *testing.T) { + statm, err := testProcStatm(26231) + if err != nil { + t.Fatal(err) + } + + if want, have := statm.Size*uint64(os.Getpagesize()), statm.SizeBytes(); want != have { + t.Errorf("want total program memory %d, have %d", want, have) + } +} + +func TestResidentBytes(t *testing.T) { + statm, err := testProcStatm(26231) + if err != nil { + t.Fatal(err) + } + + if want, have := statm.Resident*uint64(os.Getpagesize()), statm.ResidentBytes(); want != have { + t.Errorf("want resident memory %d, have %d", want, have) + } +} + +func TestSHRBytes(t *testing.T) { + statm, err := testProcStatm(26231) + if err != nil { + t.Fatal(err) + } + + if want, have := statm.Shared*uint64(os.Getpagesize()), statm.SHRBytes(); want != have { + t.Errorf("want share memory %d, have %d", want, have) + } +} + +func TestTextBytes(t *testing.T) { + statm, err := testProcStatm(26231) + if err != nil { + t.Fatal(err) + } + + if want, have := statm.Text*uint64(os.Getpagesize()), statm.TextBytes(); want != have { + t.Errorf("want text (code) size %d, have %d", want, have) + } +} + +func TestDataBytes(t *testing.T) { + statm, err := testProcStatm(26231) + if err != nil { + t.Fatal(err) + } + + if want, have := statm.Data*uint64(os.Getpagesize()), statm.DataBytes(); want != have { + t.Errorf("want data + stack size %d, have %d", want, have) + } +} + +func testProcStatm(pid int) (ProcStatm, error) { + fs, err := NewFS(procTestFixtures) + if err != nil { + return ProcStatm{}, err + } + p, err := fs.Proc(pid) + if err != nil { + return ProcStatm{}, err + } + + return p.Statm() +} diff --git a/testdata/fixtures.ttar b/testdata/fixtures.ttar index 8f6cd477..b42a5f5a 100644 --- a/testdata/fixtures.ttar +++ b/testdata/fixtures.ttar @@ -802,6 +802,11 @@ Lines: 1 26231 (vim) R 5392 7446 5392 34835 7446 4218880 32533 309516 26 82 1677 44 158 99 20 0 1 0 82375 56274944 1981 18446744073709551615 4194304 6294284 140736914091744 140736914087944 139965136429984 0 0 12288 1870679807 0 0 0 17 0 0 0 31 0 0 8391624 8481048 16420864 140736914093252 140736914093279 140736914093279 140736914096107 0 Mode: 644 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Path: fixtures/proc/26231/statm +Lines: 1 +149919 12547 18446744073709551615 19864 0 14531 0 +Mode: 644 +# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Path: fixtures/proc/26231/status Lines: 53