123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483 |
- // Copyright (c) 2012 VMware, Inc.
- // +build freebsd linux
- package gosigar
- import (
- "bufio"
- "bytes"
- "fmt"
- "io"
- "io/ioutil"
- "os"
- "os/user"
- "path/filepath"
- "strconv"
- "strings"
- "syscall"
- )
- var system struct {
- ticks uint64
- btime uint64
- }
- var Procd string
- func getLinuxBootTime() {
- // grab system boot time
- readFile(Procd+"/stat", func(line string) bool {
- if strings.HasPrefix(line, "btime") {
- system.btime, _ = strtoull(line[6:])
- return false // stop reading
- }
- return true
- })
- }
- func (self *LoadAverage) Get() error {
- line, err := ioutil.ReadFile(Procd + "/loadavg")
- if err != nil {
- return nil
- }
- fields := strings.Fields(string(line))
- self.One, _ = strconv.ParseFloat(fields[0], 64)
- self.Five, _ = strconv.ParseFloat(fields[1], 64)
- self.Fifteen, _ = strconv.ParseFloat(fields[2], 64)
- return nil
- }
- func (self *Mem) Get() error {
- table, err := parseMeminfo()
- if err != nil {
- return err
- }
- self.Total, _ = table["MemTotal"]
- self.Free, _ = table["MemFree"]
- buffers, _ := table["Buffers"]
- cached, _ := table["Cached"]
- if available, ok := table["MemAvailable"]; ok {
- // MemAvailable is in /proc/meminfo (kernel 3.14+)
- self.ActualFree = available
- } else {
- self.ActualFree = self.Free + buffers + cached
- }
- self.Used = self.Total - self.Free
- self.ActualUsed = self.Total - self.ActualFree
- return nil
- }
- func (self *Swap) Get() error {
- table, err := parseMeminfo()
- if err != nil {
- return err
- }
- self.Total, _ = table["SwapTotal"]
- self.Free, _ = table["SwapFree"]
- self.Used = self.Total - self.Free
- return nil
- }
- func (self *Cpu) Get() error {
- return readFile(Procd+"/stat", func(line string) bool {
- if len(line) > 4 && line[0:4] == "cpu " {
- parseCpuStat(self, line)
- return false
- }
- return true
- })
- }
- func (self *CpuList) Get() error {
- capacity := len(self.List)
- if capacity == 0 {
- capacity = 4
- }
- list := make([]Cpu, 0, capacity)
- err := readFile(Procd+"/stat", func(line string) bool {
- if len(line) > 3 && line[0:3] == "cpu" && line[3] != ' ' {
- cpu := Cpu{}
- parseCpuStat(&cpu, line)
- list = append(list, cpu)
- }
- return true
- })
- self.List = list
- return err
- }
- func (self *FileSystemList) Get() error {
- capacity := len(self.List)
- if capacity == 0 {
- capacity = 10
- }
- fslist := make([]FileSystem, 0, capacity)
- err := readFile(getMountTableFileName(), func(line string) bool {
- fields := strings.Fields(line)
- fs := FileSystem{}
- fs.DevName = fields[0]
- fs.DirName = fields[1]
- fs.SysTypeName = fields[2]
- fs.Options = fields[3]
- fslist = append(fslist, fs)
- return true
- })
- self.List = fslist
- return err
- }
- func (self *ProcList) Get() error {
- dir, err := os.Open(Procd)
- if err != nil {
- return err
- }
- defer dir.Close()
- const readAllDirnames = -1 // see os.File.Readdirnames doc
- names, err := dir.Readdirnames(readAllDirnames)
- if err != nil {
- return err
- }
- capacity := len(names)
- list := make([]int, 0, capacity)
- for _, name := range names {
- if name[0] < '0' || name[0] > '9' {
- continue
- }
- pid, err := strconv.Atoi(name)
- if err == nil {
- list = append(list, pid)
- }
- }
- self.List = list
- return nil
- }
- func (self *ProcState) Get(pid int) error {
- data, err := readProcFile(pid, "stat")
- if err != nil {
- return err
- }
- // Extract the comm value with is surrounded by parentheses.
- lIdx := bytes.Index(data, []byte("("))
- rIdx := bytes.LastIndex(data, []byte(")"))
- if lIdx < 0 || rIdx < 0 || lIdx >= rIdx || rIdx+2 >= len(data) {
- return fmt.Errorf("failed to extract comm for pid %d from '%v'", pid, string(data))
- }
- self.Name = string(data[lIdx+1 : rIdx])
- // Extract the rest of the fields that we are interested in.
- fields := bytes.Fields(data[rIdx+2:])
- if len(fields) <= 36 {
- return fmt.Errorf("expected more stat fields for pid %d from '%v'", pid, string(data))
- }
- interests := bytes.Join([][]byte{
- fields[0], // state
- fields[1], // ppid
- fields[2], // pgrp
- fields[4], // tty_nr
- fields[15], // priority
- fields[16], // nice
- fields[36], // processor (last processor executed on)
- }, []byte(" "))
- var state string
- _, err = fmt.Fscan(bytes.NewBuffer(interests),
- &state,
- &self.Ppid,
- &self.Pgid,
- &self.Tty,
- &self.Priority,
- &self.Nice,
- &self.Processor,
- )
- if err != nil {
- return fmt.Errorf("failed to parse stat fields for pid %d from '%v': %v", pid, string(data), err)
- }
- self.State = RunState(state[0])
- // Read /proc/[pid]/status to get the uid, then lookup uid to get username.
- status, err := getProcStatus(pid)
- if err != nil {
- return fmt.Errorf("failed to read process status for pid %d: %v", pid, err)
- }
- uids, err := getUIDs(status)
- if err != nil {
- return fmt.Errorf("failed to read process status for pid %d: %v", pid, err)
- }
- user, err := user.LookupId(uids[0])
- if err == nil {
- self.Username = user.Username
- } else {
- self.Username = uids[0]
- }
- return nil
- }
- func (self *ProcMem) Get(pid int) error {
- contents, err := readProcFile(pid, "statm")
- if err != nil {
- return err
- }
- fields := strings.Fields(string(contents))
- size, _ := strtoull(fields[0])
- self.Size = size << 12
- rss, _ := strtoull(fields[1])
- self.Resident = rss << 12
- share, _ := strtoull(fields[2])
- self.Share = share << 12
- contents, err = readProcFile(pid, "stat")
- if err != nil {
- return err
- }
- fields = strings.Fields(string(contents))
- self.MinorFaults, _ = strtoull(fields[10])
- self.MajorFaults, _ = strtoull(fields[12])
- self.PageFaults = self.MinorFaults + self.MajorFaults
- return nil
- }
- func (self *ProcTime) Get(pid int) error {
- contents, err := readProcFile(pid, "stat")
- if err != nil {
- return err
- }
- fields := strings.Fields(string(contents))
- user, _ := strtoull(fields[13])
- sys, _ := strtoull(fields[14])
- // convert to millis
- self.User = user * (1000 / system.ticks)
- self.Sys = sys * (1000 / system.ticks)
- self.Total = self.User + self.Sys
- // convert to millis
- self.StartTime, _ = strtoull(fields[21])
- self.StartTime /= system.ticks
- self.StartTime += system.btime
- self.StartTime *= 1000
- return nil
- }
- func (self *ProcArgs) Get(pid int) error {
- contents, err := readProcFile(pid, "cmdline")
- if err != nil {
- return err
- }
- bbuf := bytes.NewBuffer(contents)
- var args []string
- for {
- arg, err := bbuf.ReadBytes(0)
- if err == io.EOF {
- break
- }
- args = append(args, string(chop(arg)))
- }
- self.List = args
- return nil
- }
- func (self *ProcEnv) Get(pid int) error {
- contents, err := readProcFile(pid, "environ")
- if err != nil {
- return err
- }
- if self.Vars == nil {
- self.Vars = map[string]string{}
- }
- pairs := bytes.Split(contents, []byte{0})
- for _, kv := range pairs {
- parts := bytes.SplitN(kv, []byte{'='}, 2)
- if len(parts) != 2 {
- continue
- }
- key := string(bytes.TrimSpace(parts[0]))
- if key == "" {
- continue
- }
- self.Vars[key] = string(bytes.TrimSpace(parts[1]))
- }
- return nil
- }
- func (self *ProcExe) Get(pid int) error {
- fields := map[string]*string{
- "exe": &self.Name,
- "cwd": &self.Cwd,
- "root": &self.Root,
- }
- for name, field := range fields {
- val, err := os.Readlink(procFileName(pid, name))
- if err != nil {
- return err
- }
- *field = val
- }
- return nil
- }
- func parseMeminfo() (map[string]uint64, error) {
- table := map[string]uint64{}
- err := readFile(Procd+"/meminfo", func(line string) bool {
- fields := strings.Split(line, ":")
- if len(fields) != 2 {
- return true // skip on errors
- }
- valueUnit := strings.Fields(fields[1])
- value, err := strtoull(valueUnit[0])
- if err != nil {
- return true // skip on errors
- }
- if len(valueUnit) > 1 && valueUnit[1] == "kB" {
- value *= 1024
- }
- table[fields[0]] = value
- return true
- })
- return table, err
- }
- func readFile(file string, handler func(string) bool) error {
- contents, err := ioutil.ReadFile(file)
- if err != nil {
- return err
- }
- reader := bufio.NewReader(bytes.NewBuffer(contents))
- for {
- line, _, err := reader.ReadLine()
- if err == io.EOF {
- break
- }
- if !handler(string(line)) {
- break
- }
- }
- return nil
- }
- func strtoull(val string) (uint64, error) {
- return strconv.ParseUint(val, 10, 64)
- }
- func procFileName(pid int, name string) string {
- return Procd + "/" + strconv.Itoa(pid) + "/" + name
- }
- func readProcFile(pid int, name string) (content []byte, err error) {
- path := procFileName(pid, name)
- // Panics have been reported when reading proc files, let's recover and
- // report the path if this happens
- // See https://github.com/elastic/beats/issues/6692
- defer func() {
- if r := recover(); r != nil {
- content = nil
- err = fmt.Errorf("recovered panic when reading proc file '%s': %v", path, r)
- }
- }()
- contents, err := ioutil.ReadFile(path)
- if err != nil {
- if perr, ok := err.(*os.PathError); ok {
- if perr.Err == syscall.ENOENT {
- return nil, syscall.ESRCH
- }
- }
- }
- return contents, err
- }
- // getProcStatus reads /proc/[pid]/status which contains process status
- // information in human readable form.
- func getProcStatus(pid int) (map[string]string, error) {
- status := make(map[string]string, 42)
- path := filepath.Join(Procd, strconv.Itoa(pid), "status")
- err := readFile(path, func(line string) bool {
- fields := strings.SplitN(line, ":", 2)
- if len(fields) == 2 {
- status[fields[0]] = strings.TrimSpace(fields[1])
- }
- return true
- })
- return status, err
- }
- // getUIDs reads the "Uid" value from status and splits it into four values --
- // real, effective, saved set, and file system UIDs.
- func getUIDs(status map[string]string) ([]string, error) {
- uidLine, ok := status["Uid"]
- if !ok {
- return nil, fmt.Errorf("Uid not found in proc status")
- }
- uidStrs := strings.Fields(uidLine)
- if len(uidStrs) != 4 {
- return nil, fmt.Errorf("Uid line ('%s') did not contain four values", uidLine)
- }
- return uidStrs, nil
- }
|