dbtester/vendor/github.com/gyuho/linux-inspect/psn/proc_stat_linux.go

245 lines
5.7 KiB
Go

package psn
import (
"bufio"
"bytes"
"fmt"
"html/template"
"log"
"os/exec"
"reflect"
"strconv"
"strings"
"github.com/dustin/go-humanize"
"github.com/gyuho/linux-inspect/schema"
)
// GetProcStatByPID reads '/proc/$PID/stat' data.
func GetProcStatByPID(pid int64, up Uptime) (s Stat, err error) {
return parseProcStat(pid, up)
}
func parseProcStat(pid int64, up Uptime) (Stat, error) {
fpath := fmt.Sprintf("/proc/%d/stat", pid)
f, err := openToRead(fpath)
if err != nil {
return Stat{}, err
}
defer f.Close()
scanner := bufio.NewScanner(f)
st := &Stat{}
for scanner.Scan() {
txt := scanner.Text()
if len(txt) == 0 {
continue
}
fds := strings.Fields(txt)
for i, fv := range fds {
column := schema.ToField(schema.Stat.Columns[i].Name)
s := reflect.ValueOf(st).Elem()
if s.Kind() == reflect.Struct {
f := s.FieldByName(column)
if f.IsValid() {
if f.CanSet() {
switch schema.Stat.Columns[i].Kind {
case reflect.Uint64:
value, err := strconv.ParseUint(fv, 10, 64)
if err != nil {
return Stat{}, fmt.Errorf("%v when parsing %s %v", err, column, fv)
}
if !f.OverflowUint(value) {
f.SetUint(value)
bF := s.FieldByName(column + "BytesN")
if bF.IsValid() {
if bF.CanSet() {
bF.SetUint(value)
}
}
if vv, ok := schema.Stat.ColumnsToParse[schema.Stat.Columns[i].Name]; ok {
switch vv {
case schema.TypeBytes:
hF := s.FieldByName(column + "ParsedBytes")
if hF.IsValid() {
if hF.CanSet() {
hF.SetString(humanize.Bytes(value))
}
}
}
}
}
case reflect.Int64:
value, err := strconv.ParseInt(fv, 10, 64)
if err != nil {
return Stat{}, fmt.Errorf("%v when parsing %s %v", err, column, fv)
}
if !f.OverflowInt(value) {
f.SetInt(value)
bF := s.FieldByName(column + "BytesN")
if bF.IsValid() {
if bF.CanSet() {
bF.SetInt(value)
}
}
if vv, ok := schema.Stat.ColumnsToParse[schema.Stat.Columns[i].Name]; ok {
switch vv {
case schema.TypeBytes:
hF := s.FieldByName(column + "ParsedBytes")
if hF.IsValid() {
if hF.CanSet() {
hF.SetString(humanize.Bytes(uint64(value)))
}
}
}
}
}
case reflect.String:
f.SetString(fv)
if vv, ok := schema.Stat.ColumnsToParse[schema.Stat.Columns[i].Name]; ok {
switch vv {
case schema.TypeStatus:
hF := s.FieldByName(column + "ParsedStatus")
if hF.IsValid() {
if hF.CanSet() {
hF.SetString(convertProcStatus(fv))
}
}
}
}
}
}
}
}
}
}
if err := scanner.Err(); err != nil {
return Stat{}, err
}
return st.update(up)
}
func (s *Stat) update(up Uptime) (Stat, error) {
if s == nil {
return Stat{}, nil
}
if strings.HasPrefix(s.Comm, "(") {
s.Comm = s.Comm[1:]
}
if strings.HasSuffix(s.Comm, ")") {
s.Comm = s.Comm[:len(s.Comm)-1]
}
return *s, nil
}
const statTmpl = `
----------------------------------------
[/proc/{{.Pid}}/stat]
Name: {{.Comm}}
State: {{.StateParsedStatus}}
Pid: {{.Pid}}
Ppid: {{.Ppid}}
NumThreads: {{.NumThreads}}
Rss: {{.RssParsedBytes}} ({{.RssBytesN}})
Rsslim: {{.RsslimParsedBytes}} ({{.RsslimBytesN}})
Vsize: {{.VsizeParsedBytes}} ({{.VsizeBytesN}})
Starttime: {{.Starttime}}
Utime: {{.Utime}}
Stime: {{.Stime}}
Cutime: {{.Cutime}}
Cstime: {{.Cstime}}
Session: {{.Session}}
TtyNr: {{.TtyNr}}
Tpgid: {{.Tpgid}}
Flags: {{.Flags}}
minflt: {{.Minflt}}
cminflt: {{.Cminflt}}
majflt: {{.Majflt}}
cmajflt: {{.Cmajflt}}
priority: {{.Priority}}
nice: {{.Nice}}
itrealvalue: {{.Itrealvalue}}
startcode: {{.Startcode}}
endcode: {{.Endcode}}
startstack: {{.Startstack}}
lstkesp: {{.Kstkesp}}
lstkeip: {{.Kstkeip}}
signal: {{.Signal}}
blocked: {{.Blocked}}
sigignore: {{.Sigignore}}
sigcatch: {{.Sigcatch}}
wchan: {{.Wchan}}
nswap: {{.Nswap}}
cnswap: {{.Cnswap}}
exitSignal: {{.ExitSignal}}
processor: {{.Processor}}
rt_priority: {{.RtPriority}}
policy: {{.Policy}}
delayacct_blkio_ticks:
{{.DelayacctBlkioTicks}}
guest_time: {{.GuestTime}}
cguest_time: {{.CguestTime}}
start_data: {{.StartData}}
end_data: {{.EndData}}
start_brk: {{.StartBrk}}
arg_start: {{.ArgStart}}
arg_end: {{.ArgEnd}}
env_start: {{.EnvStart}}
env_end: {{.EnvEnd}}
exit_code: {{.ExitCode}}
----------------------------------------
`
func (s Stat) String() string {
tpl := template.Must(template.New("statTmpl").Parse(statTmpl))
buf := new(bytes.Buffer)
if err := tpl.Execute(buf, s); err != nil {
log.Fatal(err)
}
return buf.String()
}
// GetCPUPercentage returns the average CPU usage in percentage.
// http://stackoverflow.com/questions/16726779/how-do-i-get-the-total-cpu-usage-of-an-application-from-proc-pid-stat
// This sometimes differ from the one in 'top' command.
// So do not use it!
func (s Stat) GetCPUPercentage(up Uptime) (float64, error) {
totalSec := s.Utime + s.Stime
totalSec += s.Cutime + s.Cstime
out, err := exec.Command("/usr/bin/getconf", "CLK_TCK").Output()
if err != nil {
return 0, err
}
ot := strings.TrimSpace(strings.Replace(string(out), "\n", "", -1))
hertz, err := strconv.ParseUint(ot, 10, 64)
if err != nil || hertz == 0 {
return 0, err
}
tookSec := up.UptimeTotal - (float64(s.Starttime) / float64(hertz))
if hertz == 0 || tookSec == 0.0 {
return 0.0, nil
}
return 100 * ((float64(totalSec) / float64(hertz)) / float64(tookSec)), nil
}