...

Source file src/github.com/prometheus/procfs/cpuinfo.go

Documentation: github.com/prometheus/procfs

     1  // Copyright 2019 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 procfs
    18  
    19  import (
    20  	"bufio"
    21  	"bytes"
    22  	"errors"
    23  	"fmt"
    24  	"regexp"
    25  	"strconv"
    26  	"strings"
    27  
    28  	"github.com/prometheus/procfs/internal/util"
    29  )
    30  
    31  // CPUInfo contains general information about a system CPU found in /proc/cpuinfo.
    32  type CPUInfo struct {
    33  	Processor       uint
    34  	VendorID        string
    35  	CPUFamily       string
    36  	Model           string
    37  	ModelName       string
    38  	Stepping        string
    39  	Microcode       string
    40  	CPUMHz          float64
    41  	CacheSize       string
    42  	PhysicalID      string
    43  	Siblings        uint
    44  	CoreID          string
    45  	CPUCores        uint
    46  	APICID          string
    47  	InitialAPICID   string
    48  	FPU             string
    49  	FPUException    string
    50  	CPUIDLevel      uint
    51  	WP              string
    52  	Flags           []string
    53  	Bugs            []string
    54  	BogoMips        float64
    55  	CLFlushSize     uint
    56  	CacheAlignment  uint
    57  	AddressSizes    string
    58  	PowerManagement string
    59  }
    60  
    61  var (
    62  	cpuinfoClockRegexp          = regexp.MustCompile(`([\d.]+)`)
    63  	cpuinfoS390XProcessorRegexp = regexp.MustCompile(`^processor\s+(\d+):.*`)
    64  )
    65  
    66  // CPUInfo returns information about current system CPUs.
    67  // See https://www.kernel.org/doc/Documentation/filesystems/proc.txt
    68  func (fs FS) CPUInfo() ([]CPUInfo, error) {
    69  	data, err := util.ReadFileNoStat(fs.proc.Path("cpuinfo"))
    70  	if err != nil {
    71  		return nil, err
    72  	}
    73  	return parseCPUInfo(data)
    74  }
    75  
    76  func parseCPUInfoX86(info []byte) ([]CPUInfo, error) {
    77  	scanner := bufio.NewScanner(bytes.NewReader(info))
    78  
    79  	// find the first "processor" line
    80  	firstLine := firstNonEmptyLine(scanner)
    81  	if !strings.HasPrefix(firstLine, "processor") || !strings.Contains(firstLine, ":") {
    82  		return nil, fmt.Errorf("%w: Cannot parse  line: %q", ErrFileParse, firstLine)
    83  	}
    84  	field := strings.SplitN(firstLine, ": ", 2)
    85  	v, err := strconv.ParseUint(field[1], 0, 32)
    86  	if err != nil {
    87  		return nil, err
    88  	}
    89  	firstcpu := CPUInfo{Processor: uint(v)}
    90  	cpuinfo := []CPUInfo{firstcpu}
    91  	i := 0
    92  
    93  	for scanner.Scan() {
    94  		line := scanner.Text()
    95  		if !strings.Contains(line, ":") {
    96  			continue
    97  		}
    98  		field := strings.SplitN(line, ": ", 2)
    99  		switch strings.TrimSpace(field[0]) {
   100  		case "processor":
   101  			cpuinfo = append(cpuinfo, CPUInfo{}) // start of the next processor
   102  			i++
   103  			v, err := strconv.ParseUint(field[1], 0, 32)
   104  			if err != nil {
   105  				return nil, err
   106  			}
   107  			cpuinfo[i].Processor = uint(v)
   108  		case "vendor", "vendor_id":
   109  			cpuinfo[i].VendorID = field[1]
   110  		case "cpu family":
   111  			cpuinfo[i].CPUFamily = field[1]
   112  		case "model":
   113  			cpuinfo[i].Model = field[1]
   114  		case "model name":
   115  			cpuinfo[i].ModelName = field[1]
   116  		case "stepping":
   117  			cpuinfo[i].Stepping = field[1]
   118  		case "microcode":
   119  			cpuinfo[i].Microcode = field[1]
   120  		case "cpu MHz":
   121  			v, err := strconv.ParseFloat(field[1], 64)
   122  			if err != nil {
   123  				return nil, err
   124  			}
   125  			cpuinfo[i].CPUMHz = v
   126  		case "cache size":
   127  			cpuinfo[i].CacheSize = field[1]
   128  		case "physical id":
   129  			cpuinfo[i].PhysicalID = field[1]
   130  		case "siblings":
   131  			v, err := strconv.ParseUint(field[1], 0, 32)
   132  			if err != nil {
   133  				return nil, err
   134  			}
   135  			cpuinfo[i].Siblings = uint(v)
   136  		case "core id":
   137  			cpuinfo[i].CoreID = field[1]
   138  		case "cpu cores":
   139  			v, err := strconv.ParseUint(field[1], 0, 32)
   140  			if err != nil {
   141  				return nil, err
   142  			}
   143  			cpuinfo[i].CPUCores = uint(v)
   144  		case "apicid":
   145  			cpuinfo[i].APICID = field[1]
   146  		case "initial apicid":
   147  			cpuinfo[i].InitialAPICID = field[1]
   148  		case "fpu":
   149  			cpuinfo[i].FPU = field[1]
   150  		case "fpu_exception":
   151  			cpuinfo[i].FPUException = field[1]
   152  		case "cpuid level":
   153  			v, err := strconv.ParseUint(field[1], 0, 32)
   154  			if err != nil {
   155  				return nil, err
   156  			}
   157  			cpuinfo[i].CPUIDLevel = uint(v)
   158  		case "wp":
   159  			cpuinfo[i].WP = field[1]
   160  		case "flags":
   161  			cpuinfo[i].Flags = strings.Fields(field[1])
   162  		case "bugs":
   163  			cpuinfo[i].Bugs = strings.Fields(field[1])
   164  		case "bogomips":
   165  			v, err := strconv.ParseFloat(field[1], 64)
   166  			if err != nil {
   167  				return nil, err
   168  			}
   169  			cpuinfo[i].BogoMips = v
   170  		case "clflush size":
   171  			v, err := strconv.ParseUint(field[1], 0, 32)
   172  			if err != nil {
   173  				return nil, err
   174  			}
   175  			cpuinfo[i].CLFlushSize = uint(v)
   176  		case "cache_alignment":
   177  			v, err := strconv.ParseUint(field[1], 0, 32)
   178  			if err != nil {
   179  				return nil, err
   180  			}
   181  			cpuinfo[i].CacheAlignment = uint(v)
   182  		case "address sizes":
   183  			cpuinfo[i].AddressSizes = field[1]
   184  		case "power management":
   185  			cpuinfo[i].PowerManagement = field[1]
   186  		}
   187  	}
   188  	return cpuinfo, nil
   189  }
   190  
   191  func parseCPUInfoARM(info []byte) ([]CPUInfo, error) {
   192  	scanner := bufio.NewScanner(bytes.NewReader(info))
   193  
   194  	firstLine := firstNonEmptyLine(scanner)
   195  	match, err := regexp.MatchString("^[Pp]rocessor", firstLine)
   196  	if !match || !strings.Contains(firstLine, ":") {
   197  		return nil, fmt.Errorf("%s: Cannot parse line: %q: %w", ErrFileParse, firstLine, err)
   198  
   199  	}
   200  	field := strings.SplitN(firstLine, ": ", 2)
   201  	cpuinfo := []CPUInfo{}
   202  	featuresLine := ""
   203  	commonCPUInfo := CPUInfo{}
   204  	i := 0
   205  	if strings.TrimSpace(field[0]) == "Processor" {
   206  		commonCPUInfo = CPUInfo{ModelName: field[1]}
   207  		i = -1
   208  	} else {
   209  		v, err := strconv.ParseUint(field[1], 0, 32)
   210  		if err != nil {
   211  			return nil, err
   212  		}
   213  		firstcpu := CPUInfo{Processor: uint(v)}
   214  		cpuinfo = []CPUInfo{firstcpu}
   215  	}
   216  
   217  	for scanner.Scan() {
   218  		line := scanner.Text()
   219  		if !strings.Contains(line, ":") {
   220  			continue
   221  		}
   222  		field := strings.SplitN(line, ": ", 2)
   223  		switch strings.TrimSpace(field[0]) {
   224  		case "processor":
   225  			cpuinfo = append(cpuinfo, commonCPUInfo) // start of the next processor
   226  			i++
   227  			v, err := strconv.ParseUint(field[1], 0, 32)
   228  			if err != nil {
   229  				return nil, err
   230  			}
   231  			cpuinfo[i].Processor = uint(v)
   232  		case "BogoMIPS":
   233  			if i == -1 {
   234  				cpuinfo = append(cpuinfo, commonCPUInfo) // There is only one processor
   235  				i++
   236  				cpuinfo[i].Processor = 0
   237  			}
   238  			v, err := strconv.ParseFloat(field[1], 64)
   239  			if err != nil {
   240  				return nil, err
   241  			}
   242  			cpuinfo[i].BogoMips = v
   243  		case "Features":
   244  			featuresLine = line
   245  		case "model name":
   246  			cpuinfo[i].ModelName = field[1]
   247  		}
   248  	}
   249  	fields := strings.SplitN(featuresLine, ": ", 2)
   250  	for i := range cpuinfo {
   251  		cpuinfo[i].Flags = strings.Fields(fields[1])
   252  	}
   253  	return cpuinfo, nil
   254  
   255  }
   256  
   257  func parseCPUInfoS390X(info []byte) ([]CPUInfo, error) {
   258  	scanner := bufio.NewScanner(bytes.NewReader(info))
   259  
   260  	firstLine := firstNonEmptyLine(scanner)
   261  	if !strings.HasPrefix(firstLine, "vendor_id") || !strings.Contains(firstLine, ":") {
   262  		return nil, fmt.Errorf("%w: Cannot parse line: %q", ErrFileParse, firstLine)
   263  	}
   264  	field := strings.SplitN(firstLine, ": ", 2)
   265  	cpuinfo := []CPUInfo{}
   266  	commonCPUInfo := CPUInfo{VendorID: field[1]}
   267  
   268  	for scanner.Scan() {
   269  		line := scanner.Text()
   270  		if !strings.Contains(line, ":") {
   271  			continue
   272  		}
   273  		field := strings.SplitN(line, ": ", 2)
   274  		switch strings.TrimSpace(field[0]) {
   275  		case "bogomips per cpu":
   276  			v, err := strconv.ParseFloat(field[1], 64)
   277  			if err != nil {
   278  				return nil, err
   279  			}
   280  			commonCPUInfo.BogoMips = v
   281  		case "features":
   282  			commonCPUInfo.Flags = strings.Fields(field[1])
   283  		}
   284  		if strings.HasPrefix(line, "processor") {
   285  			match := cpuinfoS390XProcessorRegexp.FindStringSubmatch(line)
   286  			if len(match) < 2 {
   287  				return nil, fmt.Errorf("%w: %q", ErrFileParse, firstLine)
   288  			}
   289  			cpu := commonCPUInfo
   290  			v, err := strconv.ParseUint(match[1], 0, 32)
   291  			if err != nil {
   292  				return nil, err
   293  			}
   294  			cpu.Processor = uint(v)
   295  			cpuinfo = append(cpuinfo, cpu)
   296  		}
   297  		if strings.HasPrefix(line, "cpu number") {
   298  			break
   299  		}
   300  	}
   301  
   302  	i := 0
   303  	for scanner.Scan() {
   304  		line := scanner.Text()
   305  		if !strings.Contains(line, ":") {
   306  			continue
   307  		}
   308  		field := strings.SplitN(line, ": ", 2)
   309  		switch strings.TrimSpace(field[0]) {
   310  		case "cpu number":
   311  			i++
   312  		case "cpu MHz dynamic":
   313  			clock := cpuinfoClockRegexp.FindString(strings.TrimSpace(field[1]))
   314  			v, err := strconv.ParseFloat(clock, 64)
   315  			if err != nil {
   316  				return nil, err
   317  			}
   318  			cpuinfo[i].CPUMHz = v
   319  		case "physical id":
   320  			cpuinfo[i].PhysicalID = field[1]
   321  		case "core id":
   322  			cpuinfo[i].CoreID = field[1]
   323  		case "cpu cores":
   324  			v, err := strconv.ParseUint(field[1], 0, 32)
   325  			if err != nil {
   326  				return nil, err
   327  			}
   328  			cpuinfo[i].CPUCores = uint(v)
   329  		case "siblings":
   330  			v, err := strconv.ParseUint(field[1], 0, 32)
   331  			if err != nil {
   332  				return nil, err
   333  			}
   334  			cpuinfo[i].Siblings = uint(v)
   335  		}
   336  	}
   337  
   338  	return cpuinfo, nil
   339  }
   340  
   341  func parseCPUInfoMips(info []byte) ([]CPUInfo, error) {
   342  	scanner := bufio.NewScanner(bytes.NewReader(info))
   343  
   344  	// find the first "processor" line
   345  	firstLine := firstNonEmptyLine(scanner)
   346  	if !strings.HasPrefix(firstLine, "system type") || !strings.Contains(firstLine, ":") {
   347  		return nil, fmt.Errorf("%w: %q", ErrFileParse, firstLine)
   348  	}
   349  	field := strings.SplitN(firstLine, ": ", 2)
   350  	cpuinfo := []CPUInfo{}
   351  	systemType := field[1]
   352  
   353  	i := 0
   354  
   355  	for scanner.Scan() {
   356  		line := scanner.Text()
   357  		if !strings.Contains(line, ":") {
   358  			continue
   359  		}
   360  		field := strings.SplitN(line, ": ", 2)
   361  		switch strings.TrimSpace(field[0]) {
   362  		case "processor":
   363  			v, err := strconv.ParseUint(field[1], 0, 32)
   364  			if err != nil {
   365  				return nil, err
   366  			}
   367  			i = int(v)
   368  			cpuinfo = append(cpuinfo, CPUInfo{}) // start of the next processor
   369  			cpuinfo[i].Processor = uint(v)
   370  			cpuinfo[i].VendorID = systemType
   371  		case "cpu model":
   372  			cpuinfo[i].ModelName = field[1]
   373  		case "BogoMIPS":
   374  			v, err := strconv.ParseFloat(field[1], 64)
   375  			if err != nil {
   376  				return nil, err
   377  			}
   378  			cpuinfo[i].BogoMips = v
   379  		}
   380  	}
   381  	return cpuinfo, nil
   382  }
   383  
   384  func parseCPUInfoLoong(info []byte) ([]CPUInfo, error) {
   385  	scanner := bufio.NewScanner(bytes.NewReader(info))
   386  	// find the first "processor" line
   387  	firstLine := firstNonEmptyLine(scanner)
   388  	if !strings.HasPrefix(firstLine, "system type") || !strings.Contains(firstLine, ":") {
   389  		return nil, errors.New("invalid cpuinfo file: " + firstLine)
   390  	}
   391  	field := strings.SplitN(firstLine, ": ", 2)
   392  	cpuinfo := []CPUInfo{}
   393  	systemType := field[1]
   394  	i := 0
   395  	for scanner.Scan() {
   396  		line := scanner.Text()
   397  		if !strings.Contains(line, ":") {
   398  			continue
   399  		}
   400  		field := strings.SplitN(line, ": ", 2)
   401  		switch strings.TrimSpace(field[0]) {
   402  		case "processor":
   403  			v, err := strconv.ParseUint(field[1], 0, 32)
   404  			if err != nil {
   405  				return nil, err
   406  			}
   407  			i = int(v)
   408  			cpuinfo = append(cpuinfo, CPUInfo{}) // start of the next processor
   409  			cpuinfo[i].Processor = uint(v)
   410  			cpuinfo[i].VendorID = systemType
   411  		case "CPU Family":
   412  			cpuinfo[i].CPUFamily = field[1]
   413  		case "Model Name":
   414  			cpuinfo[i].ModelName = field[1]
   415  		}
   416  	}
   417  	return cpuinfo, nil
   418  }
   419  
   420  func parseCPUInfoPPC(info []byte) ([]CPUInfo, error) {
   421  	scanner := bufio.NewScanner(bytes.NewReader(info))
   422  
   423  	firstLine := firstNonEmptyLine(scanner)
   424  	if !strings.HasPrefix(firstLine, "processor") || !strings.Contains(firstLine, ":") {
   425  		return nil, fmt.Errorf("%w: %q", ErrFileParse, firstLine)
   426  	}
   427  	field := strings.SplitN(firstLine, ": ", 2)
   428  	v, err := strconv.ParseUint(field[1], 0, 32)
   429  	if err != nil {
   430  		return nil, err
   431  	}
   432  	firstcpu := CPUInfo{Processor: uint(v)}
   433  	cpuinfo := []CPUInfo{firstcpu}
   434  	i := 0
   435  
   436  	for scanner.Scan() {
   437  		line := scanner.Text()
   438  		if !strings.Contains(line, ":") {
   439  			continue
   440  		}
   441  		field := strings.SplitN(line, ": ", 2)
   442  		switch strings.TrimSpace(field[0]) {
   443  		case "processor":
   444  			cpuinfo = append(cpuinfo, CPUInfo{}) // start of the next processor
   445  			i++
   446  			v, err := strconv.ParseUint(field[1], 0, 32)
   447  			if err != nil {
   448  				return nil, err
   449  			}
   450  			cpuinfo[i].Processor = uint(v)
   451  		case "cpu":
   452  			cpuinfo[i].VendorID = field[1]
   453  		case "clock":
   454  			clock := cpuinfoClockRegexp.FindString(strings.TrimSpace(field[1]))
   455  			v, err := strconv.ParseFloat(clock, 64)
   456  			if err != nil {
   457  				return nil, err
   458  			}
   459  			cpuinfo[i].CPUMHz = v
   460  		}
   461  	}
   462  	return cpuinfo, nil
   463  }
   464  
   465  func parseCPUInfoRISCV(info []byte) ([]CPUInfo, error) {
   466  	scanner := bufio.NewScanner(bytes.NewReader(info))
   467  
   468  	firstLine := firstNonEmptyLine(scanner)
   469  	if !strings.HasPrefix(firstLine, "processor") || !strings.Contains(firstLine, ":") {
   470  		return nil, fmt.Errorf("%w: %q", ErrFileParse, firstLine)
   471  	}
   472  	field := strings.SplitN(firstLine, ": ", 2)
   473  	v, err := strconv.ParseUint(field[1], 0, 32)
   474  	if err != nil {
   475  		return nil, err
   476  	}
   477  	firstcpu := CPUInfo{Processor: uint(v)}
   478  	cpuinfo := []CPUInfo{firstcpu}
   479  	i := 0
   480  
   481  	for scanner.Scan() {
   482  		line := scanner.Text()
   483  		if !strings.Contains(line, ":") {
   484  			continue
   485  		}
   486  		field := strings.SplitN(line, ": ", 2)
   487  		switch strings.TrimSpace(field[0]) {
   488  		case "processor":
   489  			v, err := strconv.ParseUint(field[1], 0, 32)
   490  			if err != nil {
   491  				return nil, err
   492  			}
   493  			i = int(v)
   494  			cpuinfo = append(cpuinfo, CPUInfo{}) // start of the next processor
   495  			cpuinfo[i].Processor = uint(v)
   496  		case "hart":
   497  			cpuinfo[i].CoreID = field[1]
   498  		case "isa":
   499  			cpuinfo[i].ModelName = field[1]
   500  		}
   501  	}
   502  	return cpuinfo, nil
   503  }
   504  
   505  func parseCPUInfoDummy(_ []byte) ([]CPUInfo, error) { // nolint:unused,deadcode
   506  	return nil, errors.New("not implemented")
   507  }
   508  
   509  // firstNonEmptyLine advances the scanner to the first non-empty line
   510  // and returns the contents of that line.
   511  func firstNonEmptyLine(scanner *bufio.Scanner) string {
   512  	for scanner.Scan() {
   513  		line := scanner.Text()
   514  		if strings.TrimSpace(line) != "" {
   515  			return line
   516  		}
   517  	}
   518  	return ""
   519  }
   520  

View as plain text