github.com/elastic/gosigar@v0.14.3/sigar_linux_common.go (about)

     1  // Copyright (c) 2012 VMware, Inc.
     2  
     3  // +build freebsd linux
     4  
     5  package gosigar
     6  
     7  import (
     8  	"bufio"
     9  	"bytes"
    10  	"fmt"
    11  	"io"
    12  	"io/ioutil"
    13  	"os"
    14  	"os/user"
    15  	"path/filepath"
    16  	"strconv"
    17  	"strings"
    18  	"syscall"
    19  )
    20  
    21  var system struct {
    22  	ticks uint64
    23  	btime uint64
    24  }
    25  
    26  var Procd string
    27  
    28  func getLinuxBootTime() {
    29  	// grab system boot time
    30  	readFile(Procd+"/stat", func(line string) bool {
    31  		if strings.HasPrefix(line, "btime") {
    32  			system.btime, _ = strtoull(line[6:])
    33  			return false // stop reading
    34  		}
    35  		return true
    36  	})
    37  }
    38  
    39  func (self *LoadAverage) Get() error {
    40  	line, err := ioutil.ReadFile(Procd + "/loadavg")
    41  	if err != nil {
    42  		return nil
    43  	}
    44  
    45  	fields := strings.Fields(string(line))
    46  
    47  	self.One, _ = strconv.ParseFloat(fields[0], 64)
    48  	self.Five, _ = strconv.ParseFloat(fields[1], 64)
    49  	self.Fifteen, _ = strconv.ParseFloat(fields[2], 64)
    50  
    51  	return nil
    52  }
    53  
    54  func (self *Swap) Get() error {
    55  
    56  	table, err := parseMeminfo()
    57  	if err != nil {
    58  		return err
    59  	}
    60  	self.Total, _ = table["SwapTotal"]
    61  	self.Free, _ = table["SwapFree"]
    62  
    63  	self.Used = self.Total - self.Free
    64  	return nil
    65  }
    66  
    67  func (self *Cpu) Get() error {
    68  	return readFile(Procd+"/stat", func(line string) bool {
    69  		if len(line) > 4 && line[0:4] == "cpu " {
    70  			parseCpuStat(self, line)
    71  			return false
    72  		}
    73  		return true
    74  
    75  	})
    76  }
    77  
    78  func (self *CpuList) Get() error {
    79  	capacity := len(self.List)
    80  	if capacity == 0 {
    81  		capacity = 4
    82  	}
    83  	list := make([]Cpu, 0, capacity)
    84  
    85  	err := readFile(Procd+"/stat", func(line string) bool {
    86  		if len(line) > 3 && line[0:3] == "cpu" && line[3] != ' ' {
    87  			cpu := Cpu{}
    88  			parseCpuStat(&cpu, line)
    89  			list = append(list, cpu)
    90  		}
    91  		return true
    92  	})
    93  
    94  	self.List = list
    95  
    96  	return err
    97  }
    98  
    99  func (self *FileSystemList) Get() error {
   100  	capacity := len(self.List)
   101  	if capacity == 0 {
   102  		capacity = 10
   103  	}
   104  	fslist := make([]FileSystem, 0, capacity)
   105  
   106  	err := readFile(getMountTableFileName(), func(line string) bool {
   107  		fields := strings.Fields(line)
   108  
   109  		fs := FileSystem{}
   110  		fs.DevName = fields[0]
   111  		fs.DirName = fields[1]
   112  		fs.SysTypeName = fields[2]
   113  		fs.Options = fields[3]
   114  
   115  		fslist = append(fslist, fs)
   116  
   117  		return true
   118  	})
   119  
   120  	self.List = fslist
   121  
   122  	return err
   123  }
   124  
   125  func (self *ProcList) Get() error {
   126  	dir, err := os.Open(Procd)
   127  	if err != nil {
   128  		return err
   129  	}
   130  	defer dir.Close()
   131  
   132  	const readAllDirnames = -1 // see os.File.Readdirnames doc
   133  
   134  	names, err := dir.Readdirnames(readAllDirnames)
   135  	if err != nil {
   136  		return err
   137  	}
   138  
   139  	capacity := len(names)
   140  	list := make([]int, 0, capacity)
   141  
   142  	for _, name := range names {
   143  		if name[0] < '0' || name[0] > '9' {
   144  			continue
   145  		}
   146  		pid, err := strconv.Atoi(name)
   147  		if err == nil {
   148  			list = append(list, pid)
   149  		}
   150  	}
   151  
   152  	self.List = list
   153  
   154  	return nil
   155  }
   156  
   157  func (self *ProcState) Get(pid int) error {
   158  	data, err := readProcFile(pid, "stat")
   159  	if err != nil {
   160  		return err
   161  	}
   162  
   163  	// Extract the comm value with is surrounded by parentheses.
   164  	lIdx := bytes.Index(data, []byte("("))
   165  	rIdx := bytes.LastIndex(data, []byte(")"))
   166  	if lIdx < 0 || rIdx < 0 || lIdx >= rIdx || rIdx+2 >= len(data) {
   167  		return fmt.Errorf("failed to extract comm for pid %d from '%v'", pid, string(data))
   168  	}
   169  	self.Name = string(data[lIdx+1 : rIdx])
   170  
   171  	// Extract the rest of the fields that we are interested in.
   172  	fields := bytes.Fields(data[rIdx+2:])
   173  	if len(fields) <= 36 {
   174  		return fmt.Errorf("expected more stat fields for pid %d from '%v'", pid, string(data))
   175  	}
   176  
   177  	interests := bytes.Join([][]byte{
   178  		fields[0],  // state
   179  		fields[1],  // ppid
   180  		fields[2],  // pgrp
   181  		fields[4],  // tty_nr
   182  		fields[15], // priority
   183  		fields[16], // nice
   184  		fields[36], // processor (last processor executed on)
   185  	}, []byte(" "))
   186  
   187  	var state string
   188  	_, err = fmt.Fscan(bytes.NewBuffer(interests),
   189  		&state,
   190  		&self.Ppid,
   191  		&self.Pgid,
   192  		&self.Tty,
   193  		&self.Priority,
   194  		&self.Nice,
   195  		&self.Processor,
   196  	)
   197  	if err != nil {
   198  		return fmt.Errorf("failed to parse stat fields for pid %d from '%v': %v", pid, string(data), err)
   199  	}
   200  	self.State = RunState(state[0])
   201  
   202  	// Read /proc/[pid]/status to get the uid, then lookup uid to get username.
   203  	status, err := getProcStatus(pid)
   204  	if err != nil {
   205  		return fmt.Errorf("failed to read process status for pid %d: %v", pid, err)
   206  	}
   207  	uids, err := getUIDs(status)
   208  	if err != nil {
   209  		return fmt.Errorf("failed to read process status for pid %d: %v", pid, err)
   210  	}
   211  	user, err := user.LookupId(uids[0])
   212  	if err == nil {
   213  		self.Username = user.Username
   214  	} else {
   215  		self.Username = uids[0]
   216  	}
   217  
   218  	return nil
   219  }
   220  
   221  func (self *ProcMem) Get(pid int) error {
   222  	contents, err := readProcFile(pid, "statm")
   223  	if err != nil {
   224  		return err
   225  	}
   226  
   227  	fields := strings.Fields(string(contents))
   228  
   229  	size, _ := strtoull(fields[0])
   230  	self.Size = size << 12
   231  
   232  	rss, _ := strtoull(fields[1])
   233  	self.Resident = rss << 12
   234  
   235  	share, _ := strtoull(fields[2])
   236  	self.Share = share << 12
   237  
   238  	contents, err = readProcFile(pid, "stat")
   239  	if err != nil {
   240  		return err
   241  	}
   242  
   243  	fields = strings.Fields(string(contents))
   244  
   245  	self.MinorFaults, _ = strtoull(fields[10])
   246  	self.MajorFaults, _ = strtoull(fields[12])
   247  	self.PageFaults = self.MinorFaults + self.MajorFaults
   248  
   249  	return nil
   250  }
   251  
   252  func (self *ProcTime) Get(pid int) error {
   253  	contents, err := readProcFile(pid, "stat")
   254  	if err != nil {
   255  		return err
   256  	}
   257  
   258  	fields := strings.Fields(string(contents))
   259  
   260  	user, _ := strtoull(fields[13])
   261  	sys, _ := strtoull(fields[14])
   262  	// convert to millis
   263  	self.User = user * (1000 / system.ticks)
   264  	self.Sys = sys * (1000 / system.ticks)
   265  	self.Total = self.User + self.Sys
   266  
   267  	// convert to millis
   268  	self.StartTime, _ = strtoull(fields[21])
   269  	self.StartTime /= system.ticks
   270  	self.StartTime += system.btime
   271  	self.StartTime *= 1000
   272  
   273  	return nil
   274  }
   275  
   276  func (self *ProcArgs) Get(pid int) error {
   277  	contents, err := readProcFile(pid, "cmdline")
   278  	if err != nil {
   279  		return err
   280  	}
   281  
   282  	bbuf := bytes.NewBuffer(contents)
   283  
   284  	var args []string
   285  
   286  	for {
   287  		arg, err := bbuf.ReadBytes(0)
   288  		if err == io.EOF {
   289  			break
   290  		}
   291  		args = append(args, string(chop(arg)))
   292  	}
   293  
   294  	self.List = args
   295  
   296  	return nil
   297  }
   298  
   299  func (self *ProcEnv) Get(pid int) error {
   300  	contents, err := readProcFile(pid, "environ")
   301  	if err != nil {
   302  		return err
   303  	}
   304  
   305  	if self.Vars == nil {
   306  		self.Vars = map[string]string{}
   307  	}
   308  
   309  	pairs := bytes.Split(contents, []byte{0})
   310  	for _, kv := range pairs {
   311  		parts := bytes.SplitN(kv, []byte{'='}, 2)
   312  		if len(parts) != 2 {
   313  			continue
   314  		}
   315  
   316  		key := string(bytes.TrimSpace(parts[0]))
   317  		if key == "" {
   318  			continue
   319  		}
   320  
   321  		self.Vars[key] = string(bytes.TrimSpace(parts[1]))
   322  	}
   323  
   324  	return nil
   325  }
   326  
   327  func (self *ProcExe) Get(pid int) error {
   328  	fields := map[string]*string{
   329  		"exe":  &self.Name,
   330  		"cwd":  &self.Cwd,
   331  		"root": &self.Root,
   332  	}
   333  
   334  	for name, field := range fields {
   335  		val, err := os.Readlink(procFileName(pid, name))
   336  
   337  		if err != nil {
   338  			return err
   339  		}
   340  
   341  		*field = val
   342  	}
   343  
   344  	return nil
   345  }
   346  
   347  func parseMeminfo() (map[string]uint64, error) {
   348  	table := map[string]uint64{}
   349  
   350  	err := readFile(Procd+"/meminfo", func(line string) bool {
   351  		fields := strings.Split(line, ":")
   352  
   353  		if len(fields) != 2 {
   354  			return true // skip on errors
   355  		}
   356  
   357  		valueUnit := strings.Fields(fields[1])
   358  		value, err := strtoull(valueUnit[0])
   359  		if err != nil {
   360  			return true // skip on errors
   361  		}
   362  
   363  		if len(valueUnit) > 1 && valueUnit[1] == "kB" {
   364  			value *= 1024
   365  		}
   366  		table[fields[0]] = value
   367  
   368  		return true
   369  	})
   370  	return table, err
   371  }
   372  
   373  func readFile(file string, handler func(string) bool) error {
   374  	contents, err := ioutil.ReadFile(file)
   375  	if err != nil {
   376  		return err
   377  	}
   378  
   379  	reader := bufio.NewReader(bytes.NewBuffer(contents))
   380  
   381  	for {
   382  		line, _, err := reader.ReadLine()
   383  		if err == io.EOF {
   384  			break
   385  		}
   386  		if !handler(string(line)) {
   387  			break
   388  		}
   389  	}
   390  
   391  	return nil
   392  }
   393  
   394  func strtoull(val string) (uint64, error) {
   395  	return strconv.ParseUint(val, 10, 64)
   396  }
   397  
   398  func procFileName(pid int, name string) string {
   399  	return Procd + "/" + strconv.Itoa(pid) + "/" + name
   400  }
   401  
   402  func readProcFile(pid int, name string) (content []byte, err error) {
   403  	path := procFileName(pid, name)
   404  
   405  	// Panics have been reported when reading proc files, let's recover and
   406  	// report the path if this happens
   407  	// See https://github.com/elastic/beats/issues/6692
   408  	defer func() {
   409  		if r := recover(); r != nil {
   410  			content = nil
   411  			err = fmt.Errorf("recovered panic when reading proc file '%s': %v", path, r)
   412  		}
   413  	}()
   414  	contents, err := ioutil.ReadFile(path)
   415  
   416  	if err != nil {
   417  		if perr, ok := err.(*os.PathError); ok {
   418  			if perr.Err == syscall.ENOENT {
   419  				return nil, syscall.ESRCH
   420  			}
   421  		}
   422  	}
   423  
   424  	return contents, err
   425  }
   426  
   427  // getProcStatus reads /proc/[pid]/status which contains process status
   428  // information in human readable form.
   429  func getProcStatus(pid int) (map[string]string, error) {
   430  	status := make(map[string]string, 42)
   431  	path := filepath.Join(Procd, strconv.Itoa(pid), "status")
   432  	err := readFile(path, func(line string) bool {
   433  		fields := strings.SplitN(line, ":", 2)
   434  		if len(fields) == 2 {
   435  			status[fields[0]] = strings.TrimSpace(fields[1])
   436  		}
   437  
   438  		return true
   439  	})
   440  	return status, err
   441  }
   442  
   443  // getUIDs reads the "Uid" value from status and splits it into four values --
   444  // real, effective, saved set, and  file system UIDs.
   445  func getUIDs(status map[string]string) ([]string, error) {
   446  	uidLine, ok := status["Uid"]
   447  	if !ok {
   448  		return nil, fmt.Errorf("Uid not found in proc status")
   449  	}
   450  
   451  	uidStrs := strings.Fields(uidLine)
   452  	if len(uidStrs) != 4 {
   453  		return nil, fmt.Errorf("Uid line ('%s') did not contain four values", uidLine)
   454  	}
   455  
   456  	return uidStrs, nil
   457  }