306 lines
6.7 KiB
Go
306 lines
6.7 KiB
Go
package proc
|
|
|
|
import (
|
|
"fmt"
|
|
"github.com/prometheus/procfs"
|
|
"time"
|
|
)
|
|
|
|
func newProcIdStatic(pid, ppid int, startTime uint64, name string, cmdline []string) ProcIdStatic {
|
|
return ProcIdStatic{ProcId{pid, startTime}, ProcStatic{name, cmdline, ppid, time.Time{}}}
|
|
}
|
|
|
|
type (
|
|
// ProcId uniquely identifies a process.
|
|
ProcId struct {
|
|
// UNIX process id
|
|
Pid int
|
|
// The time the process started after system boot, the value is expressed
|
|
// in clock ticks.
|
|
StartTimeRel uint64
|
|
}
|
|
|
|
// ProcStatic contains data read from /proc/pid/*
|
|
ProcStatic struct {
|
|
Name string
|
|
Cmdline []string
|
|
ParentPid int
|
|
StartTime time.Time
|
|
}
|
|
|
|
// ProcMetrics contains data read from /proc/pid/*
|
|
ProcMetrics struct {
|
|
CpuTime float64
|
|
ReadBytes uint64
|
|
WriteBytes uint64
|
|
ResidentBytes uint64
|
|
VirtualBytes uint64
|
|
}
|
|
|
|
ProcIdStatic struct {
|
|
ProcId
|
|
ProcStatic
|
|
}
|
|
|
|
ProcInfo struct {
|
|
ProcStatic
|
|
ProcMetrics
|
|
}
|
|
|
|
ProcIdInfo struct {
|
|
ProcId
|
|
ProcStatic
|
|
ProcMetrics
|
|
}
|
|
|
|
// Proc wraps the details of the underlying procfs-reading library.
|
|
Proc interface {
|
|
// GetPid() returns the POSIX PID (process id). They may be reused over time.
|
|
GetPid() int
|
|
// GetProcId() returns (pid,starttime), which can be considered a unique process id.
|
|
// It may fail if the caller doesn't have permission to read /proc/<pid>/stat, or if
|
|
// the process has disapeared.
|
|
GetProcId() (ProcId, error)
|
|
// GetStatic() returns various details read from files under /proc/<pid>/. Technically
|
|
// name may not be static, but we'll pretend it is.
|
|
// It may fail if the caller doesn't have permission to read those files, or if
|
|
// the process has disapeared.
|
|
GetStatic() (ProcStatic, error)
|
|
// GetMetrics() returns various metrics read from files under /proc/<pid>/.
|
|
// It may fail if the caller doesn't have permission to read those files, or if
|
|
// the process has disapeared.
|
|
GetMetrics() (ProcMetrics, error)
|
|
}
|
|
|
|
// proc is a wrapper for procfs.Proc that caches results of some reads and implements Proc.
|
|
proc struct {
|
|
procfs.Proc
|
|
procid *ProcId
|
|
stat *procfs.ProcStat
|
|
cmdline []string
|
|
io *procfs.ProcIO
|
|
bootTime int64
|
|
}
|
|
|
|
procs interface {
|
|
get(int) Proc
|
|
length() int
|
|
}
|
|
|
|
procfsprocs struct {
|
|
Procs []procfs.Proc
|
|
bootTime int64
|
|
}
|
|
|
|
// ProcIter is an iterator over a sequence of procs.
|
|
ProcIter interface {
|
|
// Next returns true if the iterator is not exhausted.
|
|
Next() bool
|
|
// Close releases any resources the iterator uses.
|
|
Close() error
|
|
// The iterator satisfies the Proc interface.
|
|
Proc
|
|
}
|
|
|
|
// procIterator implements the ProcIter interface using procfs.
|
|
procIterator struct {
|
|
// procs is the list of Proc we're iterating over.
|
|
procs
|
|
// idx is the current iteration, i.e. it's an index into procs.
|
|
idx int
|
|
// err is set with an error when Next() fails. It is not affected by failures accessing
|
|
// the current iteration variable, e.g. with GetProcId.
|
|
err error
|
|
// Proc is the current iteration variable, or nil if Next() has never been called or the
|
|
// iterator is exhausted.
|
|
Proc
|
|
}
|
|
|
|
procIdInfos []ProcIdInfo
|
|
)
|
|
|
|
func procInfoIter(ps ...ProcIdInfo) ProcIter {
|
|
return &procIterator{procs: procIdInfos(ps), idx: -1}
|
|
}
|
|
|
|
func Info(p Proc) (ProcIdInfo, error) {
|
|
id, err := p.GetProcId()
|
|
if err != nil {
|
|
return ProcIdInfo{}, err
|
|
}
|
|
static, err := p.GetStatic()
|
|
if err != nil {
|
|
return ProcIdInfo{}, err
|
|
}
|
|
metrics, err := p.GetMetrics()
|
|
if err != nil {
|
|
return ProcIdInfo{}, err
|
|
}
|
|
return ProcIdInfo{id, static, metrics}, nil
|
|
}
|
|
|
|
func (p procIdInfos) get(i int) Proc {
|
|
return &p[i]
|
|
}
|
|
|
|
func (p procIdInfos) length() int {
|
|
return len(p)
|
|
}
|
|
|
|
func (p ProcIdInfo) GetPid() int {
|
|
return p.ProcId.Pid
|
|
}
|
|
|
|
func (p ProcIdInfo) GetProcId() (ProcId, error) {
|
|
return p.ProcId, nil
|
|
}
|
|
|
|
func (p ProcIdInfo) GetStatic() (ProcStatic, error) {
|
|
return p.ProcStatic, nil
|
|
}
|
|
|
|
func (p ProcIdInfo) GetMetrics() (ProcMetrics, error) {
|
|
return p.ProcMetrics, nil
|
|
}
|
|
|
|
func (p procfsprocs) get(i int) Proc {
|
|
return &proc{Proc: p.Procs[i], bootTime: p.bootTime}
|
|
}
|
|
|
|
func (p procfsprocs) length() int {
|
|
return len(p.Procs)
|
|
}
|
|
|
|
func (p *proc) GetPid() int {
|
|
return p.Proc.PID
|
|
}
|
|
|
|
func (p *proc) GetStat() (procfs.ProcStat, error) {
|
|
if p.stat == nil {
|
|
stat, err := p.Proc.NewStat()
|
|
if err != nil {
|
|
return procfs.ProcStat{}, err
|
|
}
|
|
p.stat = &stat
|
|
}
|
|
|
|
return *p.stat, nil
|
|
}
|
|
|
|
func (p *proc) GetProcId() (ProcId, error) {
|
|
if p.procid == nil {
|
|
stat, err := p.GetStat()
|
|
if err != nil {
|
|
return ProcId{}, err
|
|
}
|
|
p.procid = &ProcId{Pid: p.GetPid(), StartTimeRel: stat.Starttime}
|
|
}
|
|
|
|
return *p.procid, nil
|
|
}
|
|
|
|
func (p *proc) GetCmdLine() ([]string, error) {
|
|
if p.cmdline == nil {
|
|
cmdline, err := p.Proc.CmdLine()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
p.cmdline = cmdline
|
|
}
|
|
return p.cmdline, nil
|
|
}
|
|
|
|
func (p *proc) GetIo() (procfs.ProcIO, error) {
|
|
if p.io == nil {
|
|
io, err := p.Proc.NewIO()
|
|
if err != nil {
|
|
return procfs.ProcIO{}, err
|
|
}
|
|
p.io = &io
|
|
}
|
|
return *p.io, nil
|
|
}
|
|
|
|
func (p proc) GetStatic() (ProcStatic, error) {
|
|
cmdline, err := p.GetCmdLine()
|
|
if err != nil {
|
|
return ProcStatic{}, err
|
|
}
|
|
stat, err := p.GetStat()
|
|
if err != nil {
|
|
return ProcStatic{}, err
|
|
}
|
|
startTime := time.Unix(p.bootTime, 0)
|
|
startTime = startTime.Add(time.Second / userHZ * time.Duration(stat.Starttime))
|
|
return ProcStatic{
|
|
Name: stat.Comm,
|
|
Cmdline: cmdline,
|
|
ParentPid: stat.PPID,
|
|
StartTime: startTime,
|
|
}, nil
|
|
}
|
|
|
|
func (p proc) GetMetrics() (ProcMetrics, error) {
|
|
io, err := p.GetIo()
|
|
if err != nil {
|
|
return ProcMetrics{}, err
|
|
}
|
|
stat, err := p.GetStat()
|
|
if err != nil {
|
|
return ProcMetrics{}, err
|
|
}
|
|
return ProcMetrics{
|
|
CpuTime: stat.CPUTime(),
|
|
ReadBytes: io.ReadBytes,
|
|
WriteBytes: io.WriteBytes,
|
|
ResidentBytes: uint64(stat.ResidentMemory()),
|
|
VirtualBytes: uint64(stat.VirtualMemory()),
|
|
}, nil
|
|
}
|
|
|
|
type FS struct {
|
|
procfs.FS
|
|
BootTime int64
|
|
}
|
|
|
|
// See https://github.com/prometheus/procfs/blob/master/proc_stat.go for details on userHZ.
|
|
const userHZ = 100
|
|
|
|
// NewFS returns a new FS mounted under the given mountPoint. It will error
|
|
// if the mount point can't be read.
|
|
func NewFS(mountPoint string) (*FS, error) {
|
|
fs, err := procfs.NewFS(mountPoint)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
stat, err := fs.NewStat()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &FS{fs, stat.BootTime}, nil
|
|
}
|
|
|
|
func (fs *FS) AllProcs() ProcIter {
|
|
procs, err := fs.FS.AllProcs()
|
|
if err != nil {
|
|
err = fmt.Errorf("Error reading procs: %v", err)
|
|
}
|
|
return &procIterator{procs: procfsprocs{procs, fs.BootTime}, err: err, idx: -1}
|
|
}
|
|
|
|
func (pi *procIterator) Next() bool {
|
|
pi.idx++
|
|
if pi.idx < pi.procs.length() {
|
|
pi.Proc = pi.procs.get(pi.idx)
|
|
} else {
|
|
pi.Proc = nil
|
|
}
|
|
return pi.idx < pi.procs.length()
|
|
}
|
|
|
|
func (pi *procIterator) Close() error {
|
|
pi.Next()
|
|
pi.procs = nil
|
|
pi.Proc = nil
|
|
return pi.err
|
|
}
|