mirror of
https://github.com/DominicBreuker/pspy.git
synced 2025-12-21 03:34:50 +00:00
turn process monitoring events into structured objects
This commit is contained in:
@@ -6,6 +6,7 @@ import (
|
|||||||
|
|
||||||
"github.com/dominicbreuker/pspy/internal/config"
|
"github.com/dominicbreuker/pspy/internal/config"
|
||||||
"github.com/dominicbreuker/pspy/internal/logging"
|
"github.com/dominicbreuker/pspy/internal/logging"
|
||||||
|
"github.com/dominicbreuker/pspy/internal/psscanner"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Bindings struct {
|
type Bindings struct {
|
||||||
@@ -26,13 +27,13 @@ type FSWatcher interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type PSScanner interface {
|
type PSScanner interface {
|
||||||
Run(triggerCh chan struct{}) (chan string, chan error)
|
Run(triggerCh chan struct{}) (chan psscanner.PSEvent, chan error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type chans struct {
|
type chans struct {
|
||||||
sigCh chan os.Signal
|
sigCh chan os.Signal
|
||||||
fsEventCh chan string
|
fsEventCh chan string
|
||||||
psEventCh chan string
|
psEventCh chan psscanner.PSEvent
|
||||||
}
|
}
|
||||||
|
|
||||||
func Start(cfg *config.Config, b *Bindings, sigCh chan os.Signal) chan struct{} {
|
func Start(cfg *config.Config, b *Bindings, sigCh chan os.Signal) chan struct{} {
|
||||||
@@ -112,7 +113,7 @@ func startFSW(fsw FSWatcher, logger Logger, drainFor time.Duration) (triggerCh c
|
|||||||
return triggerCh, fsEventCh
|
return triggerCh, fsEventCh
|
||||||
}
|
}
|
||||||
|
|
||||||
func startPSS(pss PSScanner, logger Logger, triggerCh chan struct{}) (psEventCh chan string) {
|
func startPSS(pss PSScanner, logger Logger, triggerCh chan struct{}) (psEventCh chan psscanner.PSEvent) {
|
||||||
psEventCh, errCh := pss.Run(triggerCh)
|
psEventCh, errCh := pss.Run(triggerCh)
|
||||||
go logErrors(errCh, logger)
|
go logErrors(errCh, logger)
|
||||||
return psEventCh
|
return psEventCh
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
|
|
||||||
"github.com/dominicbreuker/pspy/internal/config"
|
"github.com/dominicbreuker/pspy/internal/config"
|
||||||
"github.com/dominicbreuker/pspy/internal/logging"
|
"github.com/dominicbreuker/pspy/internal/logging"
|
||||||
|
"github.com/dominicbreuker/pspy/internal/psscanner"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestInitFSW(t *testing.T) {
|
func TestInitFSW(t *testing.T) {
|
||||||
@@ -112,7 +113,7 @@ func TestStart(t *testing.T) {
|
|||||||
close(fsw.initDoneCh)
|
close(fsw.initDoneCh)
|
||||||
<-time.After(2 * drainFor)
|
<-time.After(2 * drainFor)
|
||||||
fsw.runTriggerCh <- struct{}{}
|
fsw.runTriggerCh <- struct{}{}
|
||||||
pss.runEventCh <- "pss event"
|
pss.runEventCh <- psscanner.PSEvent{UID: 1000, PID: 12345, CMD: "pss event"}
|
||||||
pss.runErrCh <- errors.New("pss error")
|
pss.runErrCh <- errors.New("pss error")
|
||||||
fsw.runEventCh <- "fsw event"
|
fsw.runEventCh <- "fsw event"
|
||||||
fsw.runErrCh <- errors.New("fsw error")
|
fsw.runErrCh <- errors.New("fsw error")
|
||||||
@@ -125,7 +126,7 @@ func TestStart(t *testing.T) {
|
|||||||
<-time.After(2 * drainFor)
|
<-time.After(2 * drainFor)
|
||||||
expectMessage(t, l.Info, "done")
|
expectMessage(t, l.Info, "done")
|
||||||
expectTrigger(t, pss.runTriggerCh) // pss receives triggers from fsw
|
expectTrigger(t, pss.runTriggerCh) // pss receives triggers from fsw
|
||||||
expectMessage(t, l.Event, fmt.Sprintf("%d CMD: pss event", logging.ColorRed))
|
expectMessage(t, l.Event, fmt.Sprintf("%d CMD: UID=1000 PID=12345 | pss event", logging.ColorRed))
|
||||||
expectMessage(t, l.Error, "ERROR: pss error")
|
expectMessage(t, l.Error, "ERROR: pss error")
|
||||||
expectMessage(t, l.Event, fmt.Sprintf("%d FS: fsw event", logging.ColorGreen))
|
expectMessage(t, l.Event, fmt.Sprintf("%d FS: fsw event", logging.ColorGreen))
|
||||||
expectMessage(t, l.Error, "ERROR: fsw error")
|
expectMessage(t, l.Error, "ERROR: fsw error")
|
||||||
@@ -255,7 +256,7 @@ func (fsw *mockFSWatcher) Run() (chan struct{}, chan string, chan error) {
|
|||||||
|
|
||||||
type mockPSScanner struct {
|
type mockPSScanner struct {
|
||||||
runTriggerCh chan struct{}
|
runTriggerCh chan struct{}
|
||||||
runEventCh chan string
|
runEventCh chan psscanner.PSEvent
|
||||||
runErrCh chan error
|
runErrCh chan error
|
||||||
numRefreshes int
|
numRefreshes int
|
||||||
}
|
}
|
||||||
@@ -264,9 +265,9 @@ func newMockPSScanner() *mockPSScanner {
|
|||||||
return &mockPSScanner{}
|
return &mockPSScanner{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pss *mockPSScanner) Run(triggerCh chan struct{}) (chan string, chan error) {
|
func (pss *mockPSScanner) Run(triggerCh chan struct{}) (chan psscanner.PSEvent, chan error) {
|
||||||
pss.runTriggerCh = triggerCh
|
pss.runTriggerCh = triggerCh
|
||||||
pss.runEventCh = make(chan string)
|
pss.runEventCh = make(chan psscanner.PSEvent)
|
||||||
pss.runErrCh = make(chan error)
|
pss.runErrCh = make(chan error)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ var cmdLineReader = func(pid int) ([]byte, error) {
|
|||||||
|
|
||||||
type procList map[int]string
|
type procList map[int]string
|
||||||
|
|
||||||
func (pl procList) refresh(eventCh chan string) error {
|
func (pl procList) refresh(eventCh chan PSEvent) error {
|
||||||
pids, err := getPIDs()
|
pids, err := getPIDs()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -74,16 +74,16 @@ func file2Pid(f os.FileInfo) (int, error) {
|
|||||||
return pid, nil
|
return pid, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pl procList) addPid(pid int, eventCh chan string) {
|
func (pl procList) addPid(pid int, eventCh chan PSEvent) {
|
||||||
cmd, err := getCmd(pid)
|
cmd, err := getCmd(pid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cmd = "???" // process probably terminated
|
cmd = "???" // process probably terminated
|
||||||
}
|
}
|
||||||
uid, err := getUID(pid)
|
uid, err := getUID(pid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
uid = "???"
|
uid = -1
|
||||||
}
|
}
|
||||||
eventCh <- fmt.Sprintf("UID=%-4s PID=%-6d | %s", uid, pid, cmd)
|
eventCh <- PSEvent{UID: uid, PID: pid, CMD: cmd}
|
||||||
pl[pid] = cmd
|
pl[pid] = cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,18 +100,26 @@ func getCmd(pid int) (string, error) {
|
|||||||
return string(cmd), nil
|
return string(cmd), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getUID(pid int) (string, error) {
|
func getUID(pid int) (int, error) {
|
||||||
stat, err := procStatusReader(pid)
|
stat, err := procStatusReader(pid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return -1, err
|
||||||
}
|
}
|
||||||
|
|
||||||
lines := strings.Split(string(stat), "\n")
|
lines := strings.Split(string(stat), "\n")
|
||||||
if len(lines) < 9 {
|
if len(lines) < 9 {
|
||||||
return "", fmt.Errorf("no uid information")
|
return -1, fmt.Errorf("no uid information")
|
||||||
}
|
}
|
||||||
|
|
||||||
uidL := strings.Split(lines[8], "\t")
|
uidL := strings.Split(lines[8], "\t")
|
||||||
if len(uidL) < 2 {
|
if len(uidL) < 2 {
|
||||||
return "", fmt.Errorf("uid line read incomplete")
|
return -1, fmt.Errorf("uid line read incomplete")
|
||||||
}
|
}
|
||||||
return uidL[1], nil
|
|
||||||
|
uid, err := strconv.Atoi(uidL[1])
|
||||||
|
if err != nil {
|
||||||
|
return -1, fmt.Errorf("converting %s to int: %v", uidL[1], err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return uid, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -136,14 +136,14 @@ func TestGetUID(t *testing.T) {
|
|||||||
pid int
|
pid int
|
||||||
stat []byte
|
stat []byte
|
||||||
statErr error
|
statErr error
|
||||||
uid string
|
uid int
|
||||||
err string
|
err string
|
||||||
}{
|
}{
|
||||||
{pid: 7, stat: completeStatus, statErr: nil, uid: "0", err: ""}, // can read normal stat
|
{pid: 7, stat: completeStatus, statErr: nil, uid: 0, err: ""}, // can read normal stat
|
||||||
{pid: 7, stat: uidLineBroken, statErr: nil, uid: "", err: "uid line read incomplete"}, // errors on incomplete Uid line
|
{pid: 7, stat: uidLineBroken, statErr: nil, uid: -1, err: "uid line read incomplete"}, // errors on incomplete Uid line
|
||||||
{pid: 7, stat: notEnoughLines, statErr: nil, uid: "", err: "no uid information"}, // errors for insufficient lines
|
{pid: 7, stat: notEnoughLines, statErr: nil, uid: -1, err: "no uid information"}, // errors for insufficient lines
|
||||||
{pid: 7, stat: []byte(""), statErr: nil, uid: "", err: "no uid information"}, // errors for insufficient lines
|
{pid: 7, stat: []byte(""), statErr: nil, uid: -1, err: "no uid information"}, // errors for insufficient lines
|
||||||
{pid: 7, stat: []byte(""), statErr: errors.New("file-system-error"), uid: "", err: "file-system-error"}, // returns file system errors
|
{pid: 7, stat: []byte(""), statErr: errors.New("file-system-error"), uid: -1, err: "file-system-error"}, // returns file system errors
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
@@ -151,7 +151,7 @@ func TestGetUID(t *testing.T) {
|
|||||||
uid, err := getUID(tt.pid)
|
uid, err := getUID(tt.pid)
|
||||||
if uid != tt.uid {
|
if uid != tt.uid {
|
||||||
fmt.Printf("STAT: %s", tt.stat)
|
fmt.Printf("STAT: %s", tt.stat)
|
||||||
t.Errorf("Wrong uid returned: got %s but want %s", uid, tt.uid)
|
t.Errorf("Wrong uid returned: got %d but want %d", uid, tt.uid)
|
||||||
}
|
}
|
||||||
if (err != nil || tt.err != "") && fmt.Sprintf("%v", err) != tt.err {
|
if (err != nil || tt.err != "") && fmt.Sprintf("%v", err) != tt.err {
|
||||||
t.Errorf("Wrong error returned: got %v but want %s", err, tt.err)
|
t.Errorf("Wrong error returned: got %v but want %s", err, tt.err)
|
||||||
@@ -174,18 +174,18 @@ func mockProcStatusReader(stat []byte, err error) (restore func()) {
|
|||||||
|
|
||||||
func TestRefresh(t *testing.T) {
|
func TestRefresh(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
eventCh chan string
|
eventCh chan PSEvent
|
||||||
pl procList
|
pl procList
|
||||||
newPids []int
|
newPids []int
|
||||||
pidsAfter []int
|
pidsAfter []int
|
||||||
events []string
|
events []string
|
||||||
}{
|
}{
|
||||||
{eventCh: make(chan string), pl: procList{}, newPids: []int{1, 2, 3}, pidsAfter: []int{3, 2, 1}, events: []string{
|
{eventCh: make(chan PSEvent), pl: procList{}, newPids: []int{1, 2, 3}, pidsAfter: []int{3, 2, 1}, events: []string{
|
||||||
"UID=??? PID=3 | the-command",
|
"UID=??? PID=3 | the-command",
|
||||||
"UID=??? PID=2 | the-command",
|
"UID=??? PID=2 | the-command",
|
||||||
"UID=??? PID=1 | the-command",
|
"UID=??? PID=1 | the-command",
|
||||||
}},
|
}},
|
||||||
{eventCh: make(chan string), pl: procList{1: "pid-found-before"}, newPids: []int{1, 2, 3}, pidsAfter: []int{1, 3, 2}, events: []string{
|
{eventCh: make(chan PSEvent), pl: procList{1: "pid-found-before"}, newPids: []int{1, 2, 3}, pidsAfter: []int{1, 3, 2}, events: []string{
|
||||||
"UID=??? PID=3 | the-command",
|
"UID=??? PID=3 | the-command",
|
||||||
"UID=??? PID=2 | the-command",
|
"UID=??? PID=2 | the-command",
|
||||||
}}, // no events emitted for PIDs already known
|
}}, // no events emitted for PIDs already known
|
||||||
@@ -200,7 +200,7 @@ func TestRefresh(t *testing.T) {
|
|||||||
done := make(chan struct{})
|
done := make(chan struct{})
|
||||||
go func() {
|
go func() {
|
||||||
for e := range tt.eventCh {
|
for e := range tt.eventCh {
|
||||||
events = append(events, e)
|
events = append(events, e.String())
|
||||||
}
|
}
|
||||||
done <- struct{}{}
|
done <- struct{}{}
|
||||||
}()
|
}()
|
||||||
|
|||||||
@@ -1,13 +1,33 @@
|
|||||||
package psscanner
|
package psscanner
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
type PSScanner struct{}
|
type PSScanner struct{}
|
||||||
|
|
||||||
|
type PSEvent struct {
|
||||||
|
UID int
|
||||||
|
PID int
|
||||||
|
CMD string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (evt PSEvent) String() string {
|
||||||
|
uid := strconv.Itoa(evt.UID)
|
||||||
|
if evt.UID == -1 {
|
||||||
|
uid = "???"
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("UID=%-4s PID=%-6d | %s", uid, evt.PID, evt.CMD)
|
||||||
|
}
|
||||||
|
|
||||||
func NewPSScanner() *PSScanner {
|
func NewPSScanner() *PSScanner {
|
||||||
return &PSScanner{}
|
return &PSScanner{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PSScanner) Run(triggerCh chan struct{}) (chan string, chan error) {
|
func (p *PSScanner) Run(triggerCh chan struct{}) (chan PSEvent, chan error) {
|
||||||
eventCh := make(chan string, 100)
|
eventCh := make(chan PSEvent, 100)
|
||||||
errCh := make(chan error)
|
errCh := make(chan error)
|
||||||
pl := make(procList)
|
pl := make(procList)
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ func TestRun(t *testing.T) {
|
|||||||
case <-time.After(timeout):
|
case <-time.After(timeout):
|
||||||
t.Errorf("did not receive event in time")
|
t.Errorf("did not receive event in time")
|
||||||
case e := <-eventCh:
|
case e := <-eventCh:
|
||||||
if e != tt.events[i] {
|
if e.String() != tt.events[i] {
|
||||||
t.Errorf("Wrong event received: got '%s' but wanted '%s'", e, tt.events[i])
|
t.Errorf("Wrong event received: got '%s' but wanted '%s'", e, tt.events[i])
|
||||||
}
|
}
|
||||||
case err := <-errCh:
|
case err := <-errCh:
|
||||||
|
|||||||
Reference in New Issue
Block a user