From 3ba15c1ddbfb0e7d9761397128ceaf84ba4e7bde Mon Sep 17 00:00:00 2001 From: Thorhallur Sverrisson Date: Mon, 6 Feb 2017 09:56:58 -0600 Subject: [PATCH] Adding support for /proc/buddyinfo for linux free memory fragmentation. /prod/buddyinfo returns data on the free blocks fragments available for use from the kernel. This data is useful when diagnosing possible memory fragmentation. More info can be found in: * https://lwn.net/Articles/7868/ * https://andorian.blogspot.com/2014/03/making-sense-of-procbuddyinfo.html --- collector/buddyinfo.go | 114 ++++++++++++++++++++++++++++++ collector/buddyinfo_test.go | 40 +++++++++++ collector/fixtures/proc/buddyinfo | 3 + 3 files changed, 157 insertions(+) create mode 100644 collector/buddyinfo.go create mode 100644 collector/buddyinfo_test.go create mode 100644 collector/fixtures/proc/buddyinfo diff --git a/collector/buddyinfo.go b/collector/buddyinfo.go new file mode 100644 index 00000000..07cc8753 --- /dev/null +++ b/collector/buddyinfo.go @@ -0,0 +1,114 @@ +// Copyright 2015 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. + +// +build !nobuddyinfo +// +build !windows,!netbsd + +package collector + +import ( + "bufio" + "fmt" + "io" + "os" + "strconv" + "strings" + + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/common/log" +) + +type buddyInfo map[string]map[string][]float64 + +const ( + buddyInfoSubsystem = "buddyinfo" +) + +type buddyinfoCollector struct { + desc *prometheus.Desc +} + +func init() { + Factories["buddyinfo"] = NewBuddyinfoCollector +} + +// NewBuddyinfoCollector returns a new Collector exposing buddyinfo stats. +func NewBuddyinfoCollector() (Collector, error) { + desc := prometheus.NewDesc( + prometheus.BuildFQName(Namespace, buddyInfoSubsystem, "count"), + "Count of free blocks according to size.", + []string{"node", "zone", "size"}, nil, + ) + return &buddyinfoCollector{desc}, nil +} + +// Update calls (*buddyinfoCollector).getBuddyInfo to get the platform specific +// buddyinfo metrics. +func (c *buddyinfoCollector) Update(ch chan<- prometheus.Metric) (err error) { + buddyInfo, err := c.getBuddyInfo() + if err != nil { + return fmt.Errorf("couldn't get buddyinfo: %s", err) + } + log.Debugf("Set node_buddy: %#v", buddyInfo) + for node, zones := range buddyInfo { + for zone, values := range zones { + for size, value := range values { + ch <- prometheus.MustNewConstMetric( + c.desc, + prometheus.GaugeValue, value, + node, zone, strconv.Itoa(size), + ) + } + } + } + return nil +} +func (c *buddyinfoCollector) getBuddyInfo() (buddyInfo, error) { + file, err := os.Open(procFilePath("buddyinfo")) + if err != nil { + return nil, err + } + defer file.Close() + + return parseBuddyInfo(file) +} + +func parseBuddyInfo(r io.Reader) (buddyInfo, error) { + var ( + buddyInfo = buddyInfo{} + scanner = bufio.NewScanner(r) + ) + + for scanner.Scan() { + var err error + line := scanner.Text() + parts := strings.Fields(string(line)) + node := strings.TrimRight(parts[1], ",") + zone := strings.TrimRight(parts[3], ",") + arraySize := len(parts[4:]) + sizes := make([]float64, arraySize) + for i := 0; i < arraySize; i++ { + sizes[i], err = strconv.ParseFloat(parts[i+4], 64) + if err != nil { + return nil, fmt.Errorf("invalid value in buddyinfo: %s", err) + } + } + + if _, ok := buddyInfo[node]; !ok { + buddyInfo[node] = make(map[string][]float64) + } + buddyInfo[node][zone] = sizes + } + + return buddyInfo, nil +} diff --git a/collector/buddyinfo_test.go b/collector/buddyinfo_test.go new file mode 100644 index 00000000..e884130e --- /dev/null +++ b/collector/buddyinfo_test.go @@ -0,0 +1,40 @@ +// Copyright 2015 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 collector + +import ( + "os" + "testing" +) + +func TestBuddyInfo(t *testing.T) { + file, err := os.Open("fixtures/proc/buddyinfo") + if err != nil { + t.Fatal(err) + } + defer file.Close() + + buddyInfo, err := parseBuddyInfo(file) + if err != nil { + t.Fatal(err) + } + + if want, got := 4381.0, buddyInfo["0"]["Normal"][0]; want != got { + t.Errorf("want Node 0, Zone Normal %f, got %f", want, got) + } + + if want, got := 572.0, buddyInfo["0"]["DMA32"][1]; want != got { + t.Errorf("want Node 0, Zone DMA32 %f, got %f", want, got) + } +} diff --git a/collector/fixtures/proc/buddyinfo b/collector/fixtures/proc/buddyinfo new file mode 100644 index 00000000..f90594a8 --- /dev/null +++ b/collector/fixtures/proc/buddyinfo @@ -0,0 +1,3 @@ +Node 0, zone DMA 1 0 1 0 2 1 1 0 1 1 3 +Node 0, zone DMA32 759 572 791 475 194 45 12 0 0 0 0 +Node 0, zone Normal 4381 1093 185 1530 567 102 4 0 0 0 0