Repository: prometheus/procfs
Pull request #224 (2019-10-11 10:47:33)

RAPL reader implementation
This adds support for reading the Running Average Power Limit values from sysfs.

https://www.kernel.org/doc/Documentation/power/powercap/powercap.txt

@discordianfish
@pgier


Patch:
diff --git a/fixtures.ttar b/fixtures.ttar
index a132eef..8b27c0d 100644
--- a/fixtures.ttar
+++ b/fixtures.ttar
@@ -2776,6 +2776,134 @@ SymlinkTo: ../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:00/PNP0C09:00
 Path: fixtures/sys/class/power_supply/BAT0
 SymlinkTo: ../../devices/LNXSYSTM:00/LNXSYBUS:00/PNP0A08:00/device:00/PNP0C09:00/PNP0C0A:00/power_supply/BAT0
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/class/powercap
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/class/powercap/intel-rapl
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/powercap/intel-rapl/enabled
+Lines: 1
+1
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/powercap/intel-rapl/uevent
+Lines: 0
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/class/powercap/intel-rapl:0
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/powercap/intel-rapl:0/constraint_0_max_power_uw
+Lines: 1
+95000000
+Mode: 444
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/powercap/intel-rapl:0/constraint_0_name
+Lines: 1
+long_term
+Mode: 444
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/powercap/intel-rapl:0/constraint_0_power_limit_uw
+Lines: 1
+4090000000
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/powercap/intel-rapl:0/constraint_0_time_window_us
+Lines: 1
+999424
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/powercap/intel-rapl:0/constraint_1_max_power_uw
+Lines: 1
+0
+Mode: 444
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/powercap/intel-rapl:0/constraint_1_name
+Lines: 1
+short_term
+Mode: 444
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/powercap/intel-rapl:0/constraint_1_power_limit_uw
+Lines: 1
+4090000000
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/powercap/intel-rapl:0/constraint_1_time_window_us
+Lines: 1
+2440
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/powercap/intel-rapl:0/enabled
+Lines: 1
+1
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/powercap/intel-rapl:0/energy_uj
+Lines: 1
+240422366267
+Mode: 444
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/powercap/intel-rapl:0/max_energy_range_uj
+Lines: 1
+262143328850
+Mode: 444
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/powercap/intel-rapl:0/name
+Lines: 1
+package-0
+Mode: 444
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/powercap/intel-rapl:0/uevent
+Lines: 0
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Directory: fixtures/sys/class/powercap/intel-rapl:0:0
+Mode: 755
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/powercap/intel-rapl:0:0/constraint_0_max_power_uw
+Lines: 0
+Mode: 444
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/powercap/intel-rapl:0:0/constraint_0_name
+Lines: 1
+long_term
+Mode: 444
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/powercap/intel-rapl:0:0/constraint_0_power_limit_uw
+Lines: 1
+0
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/powercap/intel-rapl:0:0/constraint_0_time_window_us
+Lines: 1
+976
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/powercap/intel-rapl:0:0/enabled
+Lines: 1
+0
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/powercap/intel-rapl:0:0/energy_uj
+Lines: 1
+118821284256
+Mode: 444
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/powercap/intel-rapl:0:0/max_energy_range_uj
+Lines: 1
+262143328850
+Mode: 444
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/powercap/intel-rapl:0:0/name
+Lines: 1
+core
+Mode: 444
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+Path: fixtures/sys/class/powercap/intel-rapl:0:0/uevent
+Lines: 0
+Mode: 644
+# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 Directory: fixtures/sys/class/thermal
 Mode: 775
 # ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
diff --git a/sysfs/class_powercap.go b/sysfs/class_powercap.go
new file mode 100644
index 0000000..c8b0055
--- /dev/null
+++ b/sysfs/class_powercap.go
@@ -0,0 +1,112 @@
+// Copyright 2019 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 !windows
+
+package sysfs
+
+import (
+	"errors"
+	"io/ioutil"
+	"path/filepath"
+	"strconv"
+	"strings"
+
+	"github.com/prometheus/procfs/internal/util"
+)
+
+// RaplZone stores the information for one RAPL power zone
+type RaplZone struct {
+	Name           string // name of RAPL zone from file "name"
+	Index          int    // index (different value for duplicate names)
+	Path           string // filesystem path of RaplZone
+	MaxMicrojoules uint64 // max RAPL microjoule value
+}
+
+// GetRaplZones returns a slice of RaplZones
+// When RAPL files are not present, returns nil with error
+// https://www.kernel.org/doc/Documentation/power/powercap/powercap.txt
+func GetRaplZones(fs FS) ([]RaplZone, error) {
+	raplDir := fs.sys.Path("class/powercap")
+
+	files, err := ioutil.ReadDir(raplDir)
+	if err != nil {
+		return nil, errors.New(
+			"no sysfs powercap / RAPL power metrics files found")
+	}
+
+	var zones []RaplZone
+
+	// count name usages to avoid duplicates (label them with an index)
+	countNameUsages := make(map[string]int)
+
+	// loop through directory files searching for file "name" from subdirs
+	for _, f := range files {
+		nameFile := filepath.Join(raplDir, f.Name(), "/name")
+		nameBytes, err := ioutil.ReadFile(nameFile)
+		if err == nil {
+			// add new rapl zone since name file was found
+			name := strings.TrimSpace(string(nameBytes))
+
+			// get a pair of index and final name
+			index, name := getIndexAndName(countNameUsages,
+				name)
+
+			maxMicrojouleFilename := filepath.Join(raplDir, f.Name(),
+				"/max_energy_range_uj")
+			maxMicrojoules, err := util.ReadUintFromFile(maxMicrojouleFilename)
+			if err != nil {
+				return nil, err
+			}
+
+			zone := RaplZone{
+				Name:           name,
+				Index:          index,
+				Path:           filepath.Join(raplDir, f.Name()),
+				MaxMicrojoules: maxMicrojoules,
+			}
+
+			zones = append(zones, zone)
+
+			// Store into map how many times this name has been used. There can
+			// be e.g. multiple "dram" instances without any index postfix. The
+			// count is then used for indexing
+			countNameUsages[name] = index + 1
+		}
+	}
+
+	return zones, nil
+}
+
+// GetEnergyMicrojoules returns the current microjoule value from the zone energy counter
+// https://www.kernel.org/doc/Documentation/power/powercap/powercap.txt
+func (rz RaplZone) GetEnergyMicrojoules() (uint64, error) {
+	return util.ReadUintFromFile(filepath.Join(rz.Path, "/energy_uj"))
+}
+
+// getIndexAndName returns a pair of (index, name) for a given name and name
+// counting map. Some RAPL-names have an index at the end, some have duplicates
+// without an index at the end. When the index is embedded in the name, it is
+// provided back as an integer, and stripped from the returned name. Usage
+// count is used when the index value is absent from the name.
+func getIndexAndName(countNameUsages map[string]int, name string) (int, string) {
+	length := len(name)
+	if length >= 2 {
+		index, err := strconv.Atoi(name[length-1:])
+		if name[length-2:length-1] == "-" && err == nil {
+			return index, name[:length-2]
+		}
+	}
+	// return count as the index, since name didn't have an index at the end
+	return countNameUsages[name], name
+}


Test patch:
diff --git a/sysfs/class_powercap_test.go b/sysfs/class_powercap_test.go
new file mode 100644
index 0000000..0186ade
--- /dev/null
+++ b/sysfs/class_powercap_test.go
@@ -0,0 +1,69 @@
+// Copyright 2019 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 !windows
+
+package sysfs
+
+import (
+	"path/filepath"
+	"testing"
+)
+
+func TestGetRaplZones(t *testing.T) {
+	fs, err := NewFS(sysTestFixtures)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	zones, err := GetRaplZones(fs)
+	if err != nil || zones == nil {
+		t.Fatal(err)
+	}
+}
+
+func TestNoRaplFiles(t *testing.T) {
+	// use a bad (but existing) fs path
+	fs, err := NewFS(filepath.Join(sysTestFixtures, "class"))
+	if err != nil {
+		t.Fatal(err)
+	}
+	zones, err := GetRaplZones(fs)
+	// expect failure
+	if err == nil || zones != nil {
+		t.Fatal(err)
+	}
+}
+
+func TestNewRaplValues(t *testing.T) {
+	fs, err := NewFS(sysTestFixtures)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	zones, err := GetRaplZones(fs)
+	if err != nil || zones == nil {
+		t.Fatal(err)
+	}
+
+	if len(zones) != 2 {
+		t.Fatal("wrong number of RAPL values")
+	}
+	microjoules, err := zones[0].GetEnergyMicrojoules()
+	if err != nil {
+		t.Fatal("couldn't read microjoules")
+	}
+	if microjoules != 240422366267 {
+		t.Fatal("wrong microjoule number")
+	}
+}