aboutsummaryrefslogblamecommitdiffstats
path: root/vendor/github.com/elastic/gosigar/sigar_linux_common.go
blob: 7ca649762225c1758f327b99896aa8b368136d8b (plain) (tree)




























































































































































































































































































































































































                                                                                                                 

                                                      


                                                     




                                                               


































                                                             
                                                                     
                                       









                                                                                                    











































                                                                                              
// 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
}