package main
/*
#include <time.h>
#include <unistd.h>
extern int clock_gettime(clockid_t clock_id, struct timespec *tp);
extern long sysconf(int name);
*/
import "C"
import (
"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
"time"
)
func handleErr(e error, doPanic bool) {
if e != nil && e != io.EOF {
if doPanic {
panic(e)
}
fmt.Fprintf(os.Stderr, "Error: %v\n", e)
}
}
func ticksToNsecs(ticks int64) int64 {
var hz_per_sec C.long
var secs int64
hz_per_sec = C.sysconf(C._SC_CLK_TCK)
secs = (ticks / int64(hz_per_sec))
return secs * 1e9
}
func monotonicClockTicks() int64 {
var ts C.struct_timespec
var hz_per_sec C.long
var ns int64
C.clock_gettime(C.CLOCK_MONOTONIC, &ts)
hz_per_sec = C.sysconf(C._SC_CLK_TCK)
ns = int64(ts.tv_sec) * 1e9
ns += int64(ts.tv_nsec)
return (ns / 1e9) * int64(hz_per_sec)
}
func monotonicSinceBoot() time.Duration {
var ts C.struct_timespec
C.clock_gettime(C.CLOCK_MONOTONIC, &ts)
return time.Duration(int64(ts.tv_sec*1e9) + int64(ts.tv_nsec))
}
func nullByteToSpace(b []byte) []byte {
for i, v := range b {
if v == 0x0 {
b[i] = 0x20 // ASCII space character
}
}
return b
}
func cmdLineArgs(pid int) []string {
var buf = make([]byte, 256)
var f *os.File
var err error
var n int
var cmdLinePath = fmt.Sprintf("/proc/%d/cmdline", pid)
if f, err = os.Open(cmdLinePath); err != nil {
handleErr(err, false)
}
defer f.Close()
if n, err = f.Read(buf); err != nil {
handleErr(err, false)
}
return strings.Split(string(nullByteToSpace(buf[:n])), " ")
}
func ReadFileNoStat(filename string) ([]byte, error) {
const maxBufferSize = 1024 * 512
f, err := os.Open(filename)
if err != nil {
return nil, err
}
defer f.Close()
reader := io.LimitReader(f, maxBufferSize)
return ioutil.ReadAll(reader)
}
// ProcStat is essentially parsed contents of /proc/<pid>/stat.
type ProcStat struct {
// The process ID.
PID int `json:"pid"`
// The filename of the executable.
Comm string `json:"comm"`
// The process state.
State string `json:"process_state"`
// The PID of the parent of this process.
PPID int `json:"parent_pid"`
// The process group ID of the process.
PGRP int `json:"process_group"`
// The session ID of the process.
Session int `json:"session"`
// The controlling terminal of the process.
TTY int `json:"tty"`
// The ID of the foreground process group of the controlling terminal of
// the process.
TPGID int `json:"term_process_group_id"`
// The kernel flags word of the process.
Flags uint `json:"flags"`
// The number of minor faults the process has made which have not required
// loading a memory page from disk.
MinFlt uint `json:"minor_faults"`
// The number of minor faults that the process's waited-for children have
// made.
CMinFlt uint `json:"child_minor_faults"`
// The number of major faults the process has made which have required
// loading a memory page from disk.
MajFlt uint `json:"major_faults"`
// The number of major faults that the process's waited-for children have
// made.
CMajFlt uint `json:"child_major_faults"`
// Amount of time that this process has been scheduled in user mode,
// measured in clock ticks.
UTime uint `json:"user_time_ticks"`
// Amount of time that this process has been scheduled in kernel mode,
// measured in clock ticks.
STime uint `json:"system_time_ticks"`
// Amount of time that this process's waited-for children have been
// scheduled in user mode, measured in clock ticks.
CUTime uint `json:"child_user_time_ticks"`
// Amount of time that this process's waited-for children have been
// scheduled in kernel mode, measured in clock ticks.
CSTime uint `json:"child_system_time_ticks"`
// For processes running a real-time scheduling policy, this is the negated
// scheduling priority, minus one.
Priority int `json:"priority"`
// The nice value, a value in the range 19 (low priority) to -20 (high
// priority).
Nice int `json:"nice"`
// Number of threads in this process.
NumThreads int `json:"num_threads"`
// The time the process started after system boot, the value is expressed
// in clock ticks.
Starttime uint64 `json:"starttime"`
// Virtual memory size in bytes.
VSize uint `json:"virt_memory_bytes"`
// Resident set size in pages.
RSS int `json:"rss_pages"`
}
type ProcInfo struct {
Name string `json:"name"`
Args []string `json:"args"`
PID int `json:"pid"`
AgeTicks int64 `json:"age_ticks"`
AgeDuration time.Duration `json:"age_duration"`
S *ProcStat `json:"process_stats"`
}
// Stat returns the current status information of the process.
func (p ProcInfo) Stat() (ProcStat, error) {
data, err := ReadFileNoStat(p.path("stat"))
if err != nil {
handleErr(err, true)
return ProcStat{}, err
}
var (
ignore int
s = ProcStat{PID: p.PID}
l = bytes.Index(data, []byte("("))
r = bytes.LastIndex(data, []byte(")"))
)
if l < 0 || r < 0 {
return ProcStat{}, fmt.Errorf(
"unexpected format, couldn't extract comm: %s",
data,
)
}
s.Comm = string(data[l+1 : r])
_, err = fmt.Fscan(
bytes.NewBuffer(data[r+2:]),
&s.State,
&s.PPID,
&s.PGRP,
&s.Session,
&s.TTY,
&s.TPGID,
&s.Flags,
&s.MinFlt,
&s.CMinFlt,
&s.MajFlt,
&s.CMajFlt,
&s.UTime,
&s.STime,
&s.CUTime,
&s.CSTime,
&s.Priority,
&s.Nice,
&s.NumThreads,
&ignore,
&s.Starttime,
&s.VSize,
&s.RSS,
)
if err != nil {
return ProcStat{}, err
}
return s, nil
}
func (p ProcInfo) path(name string) string {
return fmt.Sprintf("/proc/%d/%s", p.PID, name)
}
func (p ProcInfo) ProcAgeAsTicks() int64 {
return monotonicClockTicks() - int64(p.S.Starttime)
}
func (p ProcInfo) ProcAgeAsDuration() time.Duration {
return time.Duration(
monotonicSinceBoot().Nanoseconds() -
ticksToNsecs(int64(p.S.Starttime)))
}
func findProcsByName(name string) []*ProcInfo {
paths, err := filepath.Glob("/proc/[0-9]*")
handleErr(err, true)
var piSlc = make([]*ProcInfo, 0)
for _, p := range paths {
pSlc := strings.Split(p, "/")
// FIXME: Check length of slice
pid, err := strconv.Atoi(pSlc[len(pSlc)-1])
handleErr(err, true)
args := cmdLineArgs(pid)
if args[0] == name {
proci := &ProcInfo{}
proci.Args = cmdLineArgs(pid)
proci.Name = proci.Args[2]
proci.PID = pid
s, _ := proci.Stat()
proci.S = &s
proci.AgeTicks = proci.ProcAgeAsTicks()
proci.AgeDuration = proci.ProcAgeAsDuration()
piSlc = append(piSlc, proci)
}
}
return piSlc
}
func main() {
processes := findProcsByName("bro")
j, _ := json.Marshal(processes)
fmt.Printf("%v\n", string(j))
}