| // Copyright 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. |
| |
| //go:build linux |
| |
| package sysfs |
| |
| import ( |
| "fmt" |
| "os" |
| "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 := os.ReadDir(raplDir) |
| if err != nil { |
| return nil, fmt.Errorf("unable to read class/powercap: %w", err) |
| } |
| |
| 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 := os.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) { |
| s := strings.Split(name, "-") |
| if len(s) == 2 { |
| index, err := strconv.Atoi(s[1]) |
| if err == nil { |
| return index, s[0] |
| } |
| } |
| // return count as the index, since name didn't have an index at the end |
| return countNameUsages[name], name |
| } |