...

Source file src/github.com/prometheus/procfs/sysfs/system_cpu.go

Documentation: github.com/prometheus/procfs/sysfs

     1  // Copyright 2018 The Prometheus Authors
     2  // Licensed under the Apache License, Version 2.0 (the "License");
     3  // you may not use this file except in compliance with the License.
     4  // You may obtain a copy of the License at
     5  //
     6  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  //go:build linux
    15  // +build linux
    16  
    17  package sysfs
    18  
    19  import (
    20  	"fmt"
    21  	"os"
    22  	"path/filepath"
    23  	"strconv"
    24  	"strings"
    25  
    26  	"golang.org/x/sync/errgroup"
    27  
    28  	"github.com/prometheus/procfs/internal/util"
    29  )
    30  
    31  // CPU represents a path to a CPU located in `/sys/devices/system/cpu/cpu[0-9]*`.
    32  type CPU string
    33  
    34  // Number returns the ID number of the given CPU.
    35  func (c CPU) Number() string {
    36  	return strings.TrimPrefix(filepath.Base(string(c)), "cpu")
    37  }
    38  
    39  // CPUTopology contains data located in `/sys/devices/system/cpu/cpu[0-9]*/topology`.
    40  type CPUTopology struct {
    41  	CoreID             string
    42  	CoreSiblingsList   string
    43  	PhysicalPackageID  string
    44  	ThreadSiblingsList string
    45  }
    46  
    47  // CPUThermalThrottle contains data from `/sys/devices/system/cpu/cpu[0-9]*/thermal_throttle`.
    48  type CPUThermalThrottle struct {
    49  	CoreThrottleCount    uint64
    50  	PackageThrottleCount uint64
    51  }
    52  
    53  // SystemCPUCpufreqStats contains stats from `/sys/devices/system/cpu/cpu[0-9]*/cpufreq/...`.
    54  type SystemCPUCpufreqStats struct {
    55  	Name                     string
    56  	CpuinfoCurrentFrequency  *uint64
    57  	CpuinfoMinimumFrequency  *uint64
    58  	CpuinfoMaximumFrequency  *uint64
    59  	CpuinfoTransitionLatency *uint64
    60  	ScalingCurrentFrequency  *uint64
    61  	ScalingMinimumFrequency  *uint64
    62  	ScalingMaximumFrequency  *uint64
    63  	AvailableGovernors       string
    64  	Driver                   string
    65  	Governor                 string
    66  	RelatedCpus              string
    67  	SetSpeed                 string
    68  }
    69  
    70  // CPUs returns a slice of all CPUs in `/sys/devices/system/cpu`.
    71  func (fs FS) CPUs() ([]CPU, error) {
    72  	cpuPaths, err := filepath.Glob(fs.sys.Path("devices/system/cpu/cpu[0-9]*"))
    73  	if err != nil {
    74  		return nil, err
    75  	}
    76  	cpus := make([]CPU, len(cpuPaths))
    77  	for i, cpu := range cpuPaths {
    78  		cpus[i] = CPU(cpu)
    79  	}
    80  	return cpus, nil
    81  }
    82  
    83  // Topology gets the topology information for a single CPU from `/sys/devices/system/cpu/cpuN/topology`.
    84  func (c CPU) Topology() (*CPUTopology, error) {
    85  	cpuTopologyPath := filepath.Join(string(c), "topology")
    86  	if _, err := os.Stat(cpuTopologyPath); err != nil {
    87  		return nil, err
    88  	}
    89  	t, err := parseCPUTopology(cpuTopologyPath)
    90  	if err != nil {
    91  		return nil, err
    92  	}
    93  	return t, nil
    94  }
    95  
    96  func parseCPUTopology(cpuPath string) (*CPUTopology, error) {
    97  	t := CPUTopology{}
    98  	var err error
    99  	t.CoreID, err = util.SysReadFile(filepath.Join(cpuPath, "core_id"))
   100  	if err != nil {
   101  		return nil, err
   102  	}
   103  	t.PhysicalPackageID, err = util.SysReadFile(filepath.Join(cpuPath, "physical_package_id"))
   104  	if err != nil {
   105  		return nil, err
   106  	}
   107  	t.CoreSiblingsList, err = util.SysReadFile(filepath.Join(cpuPath, "core_siblings_list"))
   108  	if err != nil {
   109  		return nil, err
   110  	}
   111  	t.ThreadSiblingsList, err = util.SysReadFile(filepath.Join(cpuPath, "thread_siblings_list"))
   112  	if err != nil {
   113  		return nil, err
   114  	}
   115  	return &t, nil
   116  }
   117  
   118  // ThermalThrottle gets the cpu throttle count information for a single CPU from `/sys/devices/system/cpu/cpuN/thermal_throttle`.
   119  func (c CPU) ThermalThrottle() (*CPUThermalThrottle, error) {
   120  	cpuPath := filepath.Join(string(c), "thermal_throttle")
   121  	if _, err := os.Stat(cpuPath); err != nil {
   122  		return nil, err
   123  	}
   124  	t, err := parseCPUThermalThrottle(cpuPath)
   125  	if err != nil {
   126  		return nil, err
   127  	}
   128  	return t, nil
   129  }
   130  
   131  func parseCPUThermalThrottle(cpuPath string) (*CPUThermalThrottle, error) {
   132  	t := CPUThermalThrottle{}
   133  	var err error
   134  	t.PackageThrottleCount, err = util.ReadUintFromFile(filepath.Join(cpuPath, "package_throttle_count"))
   135  	if err != nil {
   136  		return nil, err
   137  	}
   138  	t.CoreThrottleCount, err = util.ReadUintFromFile(filepath.Join(cpuPath, "core_throttle_count"))
   139  	if err != nil {
   140  		return nil, err
   141  	}
   142  	return &t, nil
   143  }
   144  
   145  func binSearch(elem uint16, elemSlice *[]uint16) bool {
   146  	if len(*elemSlice) == 0 {
   147  		return false
   148  	}
   149  
   150  	if len(*elemSlice) == 1 {
   151  		return elem == (*elemSlice)[0]
   152  	}
   153  
   154  	start := 0
   155  	end := len(*elemSlice) - 1
   156  
   157  	var mid int
   158  
   159  	for start <= end {
   160  		mid = (start + end) / 2
   161  		if (*elemSlice)[mid] == elem {
   162  			return true
   163  		} else if (*elemSlice)[mid] > elem {
   164  			end = mid - 1
   165  		} else if (*elemSlice)[mid] < elem {
   166  			start = mid + 1
   167  		}
   168  	}
   169  
   170  	return false
   171  }
   172  
   173  func filterOfflineCPUs(offlineCpus *[]uint16, cpus *[]string) ([]string, error) {
   174  	var filteredCPUs []string
   175  	for _, cpu := range *cpus {
   176  		cpuName := strings.TrimPrefix(filepath.Base(cpu), "cpu")
   177  		cpuNameUint16, err := strconv.Atoi(cpuName)
   178  		if err != nil {
   179  			return nil, err
   180  		}
   181  		if !binSearch(uint16(cpuNameUint16), offlineCpus) {
   182  			filteredCPUs = append(filteredCPUs, cpu)
   183  		}
   184  	}
   185  
   186  	return filteredCPUs, nil
   187  }
   188  
   189  // SystemCpufreq returns CPU frequency metrics for all CPUs.
   190  func (fs FS) SystemCpufreq() ([]SystemCPUCpufreqStats, error) {
   191  	var g errgroup.Group
   192  
   193  	cpus, err := filepath.Glob(fs.sys.Path("devices/system/cpu/cpu[0-9]*"))
   194  	if err != nil {
   195  		return nil, err
   196  	}
   197  
   198  	line, err := util.ReadFileNoStat(fs.sys.Path("devices/system/cpu/offline"))
   199  	if err != nil {
   200  		return nil, err
   201  	}
   202  
   203  	if strings.TrimSpace(string(line)) != "" {
   204  		offlineCPUs, err := parseCPURange(line)
   205  		if err != nil {
   206  			return nil, err
   207  		}
   208  
   209  		if len(offlineCPUs) > 0 {
   210  			cpus, err = filterOfflineCPUs(&offlineCPUs, &cpus)
   211  			if err != nil {
   212  				return nil, err
   213  			}
   214  		}
   215  	}
   216  
   217  	systemCpufreq := make([]SystemCPUCpufreqStats, len(cpus))
   218  	for i, cpu := range cpus {
   219  		cpuName := strings.TrimPrefix(filepath.Base(cpu), "cpu")
   220  
   221  		cpuCpufreqPath := filepath.Join(cpu, "cpufreq")
   222  		_, err = os.Stat(cpuCpufreqPath)
   223  		if os.IsNotExist(err) {
   224  			continue
   225  		} else if err != nil {
   226  			return nil, err
   227  		}
   228  
   229  		// Execute the parsing of each CPU in parallel.
   230  		// This is done because the kernel intentionally delays access to each CPU by
   231  		// 50 milliseconds to avoid DDoSing possibly expensive functions.
   232  		i := i // https://golang.org/doc/faq#closures_and_goroutines
   233  		g.Go(func() error {
   234  			cpufreq, err := parseCpufreqCpuinfo(cpuCpufreqPath)
   235  			if err == nil {
   236  				cpufreq.Name = cpuName
   237  				systemCpufreq[i] = *cpufreq
   238  			}
   239  			return err
   240  		})
   241  	}
   242  
   243  	if err = g.Wait(); err != nil {
   244  		return nil, err
   245  	}
   246  
   247  	if len(systemCpufreq) == 0 {
   248  		return nil, fmt.Errorf("could not find any cpufreq files: %w", os.ErrNotExist)
   249  	}
   250  
   251  	return systemCpufreq, nil
   252  }
   253  
   254  func parseCpufreqCpuinfo(cpuPath string) (*SystemCPUCpufreqStats, error) {
   255  	uintFiles := []string{
   256  		"cpuinfo_cur_freq",
   257  		"cpuinfo_max_freq",
   258  		"cpuinfo_min_freq",
   259  		"cpuinfo_transition_latency",
   260  		"scaling_cur_freq",
   261  		"scaling_max_freq",
   262  		"scaling_min_freq",
   263  	}
   264  	uintOut := make([]*uint64, len(uintFiles))
   265  
   266  	for i, f := range uintFiles {
   267  		v, err := util.ReadUintFromFile(filepath.Join(cpuPath, f))
   268  		if err != nil {
   269  			if os.IsNotExist(err) || os.IsPermission(err) {
   270  				continue
   271  			}
   272  			return &SystemCPUCpufreqStats{}, err
   273  		}
   274  
   275  		uintOut[i] = &v
   276  	}
   277  
   278  	stringFiles := []string{
   279  		"scaling_available_governors",
   280  		"scaling_driver",
   281  		"scaling_governor",
   282  		"related_cpus",
   283  		"scaling_setspeed",
   284  	}
   285  	stringOut := make([]string, len(stringFiles))
   286  	var err error
   287  
   288  	for i, f := range stringFiles {
   289  		stringOut[i], err = util.SysReadFile(filepath.Join(cpuPath, f))
   290  		if err != nil {
   291  			return &SystemCPUCpufreqStats{}, err
   292  		}
   293  	}
   294  
   295  	return &SystemCPUCpufreqStats{
   296  		CpuinfoCurrentFrequency:  uintOut[0],
   297  		CpuinfoMaximumFrequency:  uintOut[1],
   298  		CpuinfoMinimumFrequency:  uintOut[2],
   299  		CpuinfoTransitionLatency: uintOut[3],
   300  		ScalingCurrentFrequency:  uintOut[4],
   301  		ScalingMaximumFrequency:  uintOut[5],
   302  		ScalingMinimumFrequency:  uintOut[6],
   303  		AvailableGovernors:       stringOut[0],
   304  		Driver:                   stringOut[1],
   305  		Governor:                 stringOut[2],
   306  		RelatedCpus:              stringOut[3],
   307  		SetSpeed:                 stringOut[4],
   308  	}, nil
   309  }
   310  
   311  func (fs FS) IsolatedCPUs() ([]uint16, error) {
   312  	isolcpus, err := os.ReadFile(fs.sys.Path("devices/system/cpu/isolated"))
   313  	if err != nil {
   314  		return nil, err
   315  	}
   316  
   317  	return parseCPURange(isolcpus)
   318  }
   319  
   320  func parseCPURange(data []byte) ([]uint16, error) {
   321  
   322  	var cpusInt = []uint16{}
   323  
   324  	for _, cpu := range strings.Split(strings.TrimRight(string(data), "\n"), ",") {
   325  		if cpu == "" {
   326  			continue
   327  		}
   328  		if strings.Contains(cpu, "-") {
   329  			ranges := strings.Split(cpu, "-")
   330  			if len(ranges) != 2 {
   331  				return nil, fmt.Errorf("invalid cpu range: %s", cpu)
   332  			}
   333  			startRange, err := strconv.Atoi(ranges[0])
   334  			if err != nil {
   335  				return nil, fmt.Errorf("invalid cpu start range: %w", err)
   336  			}
   337  			endRange, err := strconv.Atoi(ranges[1])
   338  			if err != nil {
   339  				return nil, fmt.Errorf("invalid cpu end range: %w", err)
   340  			}
   341  
   342  			for i := startRange; i <= endRange; i++ {
   343  				cpusInt = append(cpusInt, uint16(i))
   344  			}
   345  			continue
   346  		}
   347  
   348  		cpuN, err := strconv.Atoi(cpu)
   349  		if err != nil {
   350  			return nil, err
   351  		}
   352  		cpusInt = append(cpusInt, uint16(cpuN))
   353  	}
   354  	return cpusInt, nil
   355  }
   356  

View as plain text