Implement rudimentary 'ps' with --json flag.
--json mode doesn't have any fancy command line arguments yet. Just outputs to raw JSON. Seems to work pretty well with Underscore. Also added some structure; we can work out how the build system will work later.
This commit is contained in:
parent
77f040729a
commit
9525516e37
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
bin/
|
||||
pkg/
|
||||
|
22
src/jsh/common.go
Normal file
22
src/jsh/common.go
Normal file
@ -0,0 +1,22 @@
|
||||
/* Common structures and methods used across all coreutils */
|
||||
|
||||
package jsh
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
type JshOutput struct {
|
||||
StdOut interface{}
|
||||
StdErr interface{}
|
||||
}
|
||||
|
||||
// Converts a Jshoutput into a JSON string
|
||||
func (j JshOutput) ToJson() *string {
|
||||
jsonOut, err := json.Marshal(j)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
jsonString := string(jsonOut)
|
||||
return &jsonString
|
||||
}
|
38
src/jsh/process.go
Normal file
38
src/jsh/process.go
Normal file
@ -0,0 +1,38 @@
|
||||
/* File for Process-related structs and methods */
|
||||
|
||||
package jsh
|
||||
|
||||
import (
|
||||
"errors"
|
||||
)
|
||||
|
||||
// Process is a struct for marshalling into JSON output for the ps utility.
|
||||
type Process struct {
|
||||
User string
|
||||
Pid string
|
||||
PercentCpu string
|
||||
PercentMem string
|
||||
Vsz string
|
||||
Rss string
|
||||
Tty string
|
||||
Stat string
|
||||
Start string
|
||||
Time string
|
||||
Command string
|
||||
}
|
||||
|
||||
// NewProcess
|
||||
func NewProcess(args []string) (procPtr *Process, err error) {
|
||||
err = nil
|
||||
// 11 = number of fields in the Process struct
|
||||
if len(args) != 11 {
|
||||
procPtr = &Process{}
|
||||
err = errors.New("Unexpected number of columns")
|
||||
} else {
|
||||
// TODO: figure out nicer way to do this
|
||||
procPtr = &Process{
|
||||
args[0], args[1], args[2], args[3], args[4], args[5],
|
||||
args[6], args[7], args[8], args[9], args[10]}
|
||||
}
|
||||
return
|
||||
}
|
106
src/jsh/ps/main.go
Normal file
106
src/jsh/ps/main.go
Normal file
@ -0,0 +1,106 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"jsh"
|
||||
"math"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// FieldsN slices s into substrings after each instance of a whitespace
|
||||
// character (according to unicode.IsSpace) and returns a slice of those
|
||||
// substrings. The slice's length is guaranteed to be at most maxN, and
|
||||
// all leftover fields are put into the last substring.
|
||||
func FieldsN(s string, maxN int) []string {
|
||||
// First count the fields.
|
||||
n := 0
|
||||
inField := false
|
||||
for _, rune := range s {
|
||||
wasInField := inField
|
||||
inField = !unicode.IsSpace(rune)
|
||||
if inField && !wasInField {
|
||||
n++
|
||||
}
|
||||
}
|
||||
n = int(math.Min(float64(n), float64(maxN)))
|
||||
|
||||
// Now create them.
|
||||
a := make([]string, n)
|
||||
na := 0
|
||||
fieldStart := -1 // Set to -1 when looking for start of field.
|
||||
for i, rune := range s {
|
||||
if unicode.IsSpace(rune) && na+1 < maxN {
|
||||
if fieldStart >= 0 {
|
||||
a[na] = s[fieldStart:i]
|
||||
na++
|
||||
fieldStart = -1
|
||||
}
|
||||
} else if fieldStart == -1 {
|
||||
fieldStart = i
|
||||
}
|
||||
}
|
||||
if fieldStart >= 0 { // Last field might end at EOF.
|
||||
a[na] = s[fieldStart:]
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
// Falls back to procps-ng passing the space-separated arguments in the given
|
||||
// args slice. If args is nil, we default to the arguments passed to the command
|
||||
// line using os.Args
|
||||
func fallbackCompletelyWithArgs(args []string) *[]byte {
|
||||
if args == nil {
|
||||
args = os.Args[1:]
|
||||
}
|
||||
out, err := exec.Command("/usr/bin/ps", args...).Output()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return &out
|
||||
}
|
||||
|
||||
// Falls back to procps-ng with no default arguments
|
||||
func fallbackCompletely() *[]byte {
|
||||
return fallbackCompletelyWithArgs(nil)
|
||||
}
|
||||
|
||||
// Converts raw output of "ps" into a slice of Process objects
|
||||
func psOutputToProcesses(out string) *[]jsh.Process {
|
||||
processes := []jsh.Process{}
|
||||
lines := strings.Split(out, "\n")
|
||||
header, procs := lines[0], lines[1:]
|
||||
numFields := len(strings.Fields(header))
|
||||
|
||||
for _, proc := range procs {
|
||||
p, err := jsh.NewProcess(FieldsN(proc, numFields))
|
||||
if err == nil {
|
||||
processes = append(processes, *p)
|
||||
}
|
||||
}
|
||||
return &processes
|
||||
}
|
||||
|
||||
func runJsonMode() {
|
||||
// Run procps-ng "ps" with full output
|
||||
psOut := string(*fallbackCompletelyWithArgs([]string{"auxww"}))
|
||||
|
||||
processesPtr := psOutputToProcesses(psOut)
|
||||
finalOut := jsh.JshOutput{*processesPtr, []string{}}
|
||||
fmt.Printf("%s", *finalOut.ToJson())
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Parse for JSON flag.
|
||||
jsonModePtr := flag.Bool("json", false, "a bool")
|
||||
flag.Parse()
|
||||
|
||||
if !*jsonModePtr {
|
||||
fmt.Printf("%s", fallbackCompletely())
|
||||
} else {
|
||||
runJsonMode()
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user