Skip to content

Commit

Permalink
added a "timed" top command that runs for X seconds and dumps output
Browse files Browse the repository at this point in the history
  • Loading branch information
kent007 committed Nov 12, 2020
1 parent f784010 commit c7f603a
Show file tree
Hide file tree
Showing 36 changed files with 77 additions and 4,634 deletions.
13 changes: 9 additions & 4 deletions top/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,22 +121,27 @@ func topRowToSkip(data []byte) bool {
}

// Parse parses 'top' command output and returns the rows.
func Parse(s string) ([]Row, error) {
func Parse(s string) ([]Row, int, error) {
lines := strings.Split(s, "\n")
rows := make([][]string, 0, len(lines))
iterations := 0
for _, line := range lines {
line = strings.TrimSpace(line)
if len(line) == 0 {
continue
}
if topRowToSkip([]byte(line)) {
//it's a header line, specifically keep track of each starting header for iteration count
if strings.HasPrefix(line, "top -") {
iterations++
}
continue
}

row := strings.Fields(strings.TrimSpace(line))
if len(row) < len(Headers) {
//too short
return nil, fmt.Errorf("unexpected row column number %v (expected %v)", row, Headers)
return nil, iterations, fmt.Errorf("unexpected row column number %v (expected %v)", row, Headers)
} else if len(row) > len(Headers) {
//command had some spaces in it that got cut up into separate commands by the parser
command := strings.Join(row[len(Headers)-1:], " ")
Expand All @@ -162,12 +167,12 @@ func Parse(s string) ([]Row, error) {
select {
case rs := <-rc:
if rs.err != nil {
return nil, rs.err
return nil, iterations, rs.err
}
tcRows = append(tcRows, rs.row)
}
}
return tcRows, nil
return tcRows, iterations, nil
}

func parseRow(row []string) (Row, error) {
Expand Down
52 changes: 49 additions & 3 deletions top/top.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"fmt"
"io"
"os/exec"
"time"

"github.com/kent007/linux-inspect/pkg/fileutil"
)
Expand Down Expand Up @@ -96,7 +97,7 @@ func (cfg *Config) createCmd() error {
// Get returns all entries in 'top' command.
// If pid<1, it reads all processes in 'top' command.
// This is one-time command.
func Get(topPath string, pid int64, limit int, interval float64) ([]Row, error) {
func Get(topPath string, pid int64, limit int, interval float64) ([]Row, int, error) {
buf := new(bytes.Buffer)
cfg := &Config{
Exec: topPath,
Expand All @@ -114,12 +115,57 @@ func Get(topPath string, pid int64, limit int, interval float64) ([]Row, error)
}

if err := cfg.createCmd(); err != nil {
return nil, err
return nil, -1, err
}

// run starts the 'top' command and waits for it to complete.
if err := cfg.cmd.Run(); err != nil {
return nil, err
return nil, -1, err
}
return Parse(buf.String())
}

// this version of TOP runs until a certain unix nano timestamp occurs, then terminates
// note that because of how top runs, the first measurement only take ~100ms and following measurements take
// 'interval' seconds
// this will theoretically run indefinitely -- not suggested for large timeouts, as the output buffer may become large
func GetTimed(topPath string, pid int64, stopTimestampNano int64, interval float64) ([]Row, int, error) {
buf := new(bytes.Buffer)
cfg := &Config{
Exec: topPath,
Limit: 0,
IntervalSecond: interval,
PID: pid,
Writer: buf,
cmd: nil,
}
if cfg.IntervalSecond <= 0 {
cfg.IntervalSecond = 1
}

if err := cfg.createCmd(); err != nil {
return nil, -1, err
}
duration := time.Until(time.Unix(0, stopTimestampNano))
timeout := time.After(duration)

result := make(chan error)

_ = cfg.cmd.Start()
//put the result from the command on a channel I can select against
go func() {
result <- cfg.cmd.Wait()
}()

select {
case <-timeout:
//this has a high possibility of happening anyway, we'll wait for exit to finish before we're done
_ = cfg.cmd.Process.Kill()
<-result
case e := <-result:
if e != nil {
return nil, -1, e
}
}
return Parse(buf.String())
}
21 changes: 19 additions & 2 deletions top/top_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,35 @@ package top

import (
"fmt"
"log"
"testing"
"time"
)

func TestGet(t *testing.T) {
now := time.Now()
rows, err := Get(DefaultExecPath, 0, 2, 0)
rows, iterations, err := Get(DefaultExecPath, 0, 1, .5)
if err != nil {
t.Skip(err)
}
for _, elem := range rows {
fmt.Printf("%+v\n", elem)
}
fmt.Printf("found %d entries in %v", len(rows), time.Since(now))
fmt.Printf("found %d entries in %d iterations over %v", len(rows), iterations, time.Since(now))
}

func TestTimedGet(t *testing.T) {
now := time.Now()
log.Printf("starting test at %s", now.String())
stopTimestamp := now.Add(3 * time.Second).UnixNano()
rows, iterations, err := GetTimed(DefaultExecPath, 0, stopTimestamp, 1)

if err != nil {
t.Skip(err)
}
//for _, elem := range rows {
// fmt.Printf("%+v\n", elem)
//}
log.Printf("ending test at %s", time.Now().String())
log.Printf("found %d entries in %d iterations over %v", len(rows), iterations, time.Since(now))
}
Empty file modified vendor/github.com/gyuho/dataframe/test
100755 → 100644
Empty file.
Empty file modified vendor/github.com/kr/pty/mktypes.bash
100755 → 100644
Empty file.
10 changes: 0 additions & 10 deletions vendor/github.com/kr/pty/types.go

This file was deleted.

17 changes: 0 additions & 17 deletions vendor/github.com/kr/pty/types_dragonfly.go

This file was deleted.

15 changes: 0 additions & 15 deletions vendor/github.com/kr/pty/types_freebsd.go

This file was deleted.

14 changes: 0 additions & 14 deletions vendor/github.com/kr/pty/types_openbsd.go

This file was deleted.

Loading

0 comments on commit c7f603a

Please sign in to comment.