Browse Source

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.
Ian Adam Naval 5 years ago
parent
commit
9525516e37
4 changed files with 169 additions and 0 deletions
  1. 3
    0
      .gitignore
  2. 22
    0
      src/jsh/common.go
  3. 38
    0
      src/jsh/process.go
  4. 106
    0
      src/jsh/ps/main.go

+ 3
- 0
.gitignore View File

@@ -0,0 +1,3 @@
1
+bin/
2
+pkg/
3
+

+ 22
- 0
src/jsh/common.go View File

@@ -0,0 +1,22 @@
1
+/* Common structures and methods used across all coreutils */
2
+
3
+package jsh
4
+
5
+import (
6
+	"encoding/json"
7
+)
8
+
9
+type JshOutput struct {
10
+	StdOut interface{}
11
+	StdErr interface{}
12
+}
13
+
14
+// Converts a Jshoutput into a JSON string
15
+func (j JshOutput) ToJson() *string {
16
+	jsonOut, err := json.Marshal(j)
17
+	if err != nil {
18
+		panic(err)
19
+	}
20
+	jsonString := string(jsonOut)
21
+	return &jsonString
22
+}

+ 38
- 0
src/jsh/process.go View File

@@ -0,0 +1,38 @@
1
+/* File for Process-related structs and methods */
2
+
3
+package jsh
4
+
5
+import (
6
+	"errors"
7
+)
8
+
9
+// Process is a struct for marshalling into JSON output for the ps utility.
10
+type Process struct {
11
+	User       string
12
+	Pid        string
13
+	PercentCpu string
14
+	PercentMem string
15
+	Vsz        string
16
+	Rss        string
17
+	Tty        string
18
+	Stat       string
19
+	Start      string
20
+	Time       string
21
+	Command    string
22
+}
23
+
24
+// NewProcess
25
+func NewProcess(args []string) (procPtr *Process, err error) {
26
+	err = nil
27
+	// 11 = number of fields in the Process struct
28
+	if len(args) != 11 {
29
+		procPtr = &Process{}
30
+		err = errors.New("Unexpected number of columns")
31
+	} else {
32
+		// TODO: figure out nicer way to do this
33
+		procPtr = &Process{
34
+			args[0], args[1], args[2], args[3], args[4], args[5],
35
+			args[6], args[7], args[8], args[9], args[10]}
36
+	}
37
+	return
38
+}

+ 106
- 0
src/jsh/ps/main.go View File

@@ -0,0 +1,106 @@
1
+package main
2
+
3
+import (
4
+	"flag"
5
+	"fmt"
6
+	"jsh"
7
+	"math"
8
+	"os"
9
+	"os/exec"
10
+	"strings"
11
+	"unicode"
12
+)
13
+
14
+// FieldsN slices s into substrings after each instance of a whitespace
15
+// character (according to unicode.IsSpace) and returns a slice of those
16
+// substrings. The slice's length is guaranteed to be at most maxN, and
17
+// all leftover fields are put into the last substring.
18
+func FieldsN(s string, maxN int) []string {
19
+	// First count the fields.
20
+	n := 0
21
+	inField := false
22
+	for _, rune := range s {
23
+		wasInField := inField
24
+		inField = !unicode.IsSpace(rune)
25
+		if inField && !wasInField {
26
+			n++
27
+		}
28
+	}
29
+	n = int(math.Min(float64(n), float64(maxN)))
30
+
31
+	// Now create them.
32
+	a := make([]string, n)
33
+	na := 0
34
+	fieldStart := -1 // Set to -1 when looking for start of field.
35
+	for i, rune := range s {
36
+		if unicode.IsSpace(rune) && na+1 < maxN {
37
+			if fieldStart >= 0 {
38
+				a[na] = s[fieldStart:i]
39
+				na++
40
+				fieldStart = -1
41
+			}
42
+		} else if fieldStart == -1 {
43
+			fieldStart = i
44
+		}
45
+	}
46
+	if fieldStart >= 0 { // Last field might end at EOF.
47
+		a[na] = s[fieldStart:]
48
+	}
49
+	return a
50
+}
51
+
52
+// Falls back to procps-ng passing the space-separated arguments in the given
53
+// args slice. If args is nil, we default to the arguments passed to the command
54
+// line using os.Args
55
+func fallbackCompletelyWithArgs(args []string) *[]byte {
56
+	if args == nil {
57
+		args = os.Args[1:]
58
+	}
59
+	out, err := exec.Command("/usr/bin/ps", args...).Output()
60
+	if err != nil {
61
+		panic(err)
62
+	}
63
+	return &out
64
+}
65
+
66
+// Falls back to procps-ng with no default arguments
67
+func fallbackCompletely() *[]byte {
68
+	return fallbackCompletelyWithArgs(nil)
69
+}
70
+
71
+// Converts raw output of "ps" into a slice of Process objects
72
+func psOutputToProcesses(out string) *[]jsh.Process {
73
+	processes := []jsh.Process{}
74
+	lines := strings.Split(out, "\n")
75
+	header, procs := lines[0], lines[1:]
76
+	numFields := len(strings.Fields(header))
77
+
78
+	for _, proc := range procs {
79
+		p, err := jsh.NewProcess(FieldsN(proc, numFields))
80
+		if err == nil {
81
+			processes = append(processes, *p)
82
+		}
83
+	}
84
+	return &processes
85
+}
86
+
87
+func runJsonMode() {
88
+	// Run procps-ng "ps" with full output
89
+	psOut := string(*fallbackCompletelyWithArgs([]string{"auxww"}))
90
+
91
+	processesPtr := psOutputToProcesses(psOut)
92
+	finalOut := jsh.JshOutput{*processesPtr, []string{}}
93
+	fmt.Printf("%s", *finalOut.ToJson())
94
+}
95
+
96
+func main() {
97
+	// Parse for JSON flag.
98
+	jsonModePtr := flag.Bool("json", false, "a bool")
99
+	flag.Parse()
100
+
101
+	if !*jsonModePtr {
102
+		fmt.Printf("%s", fallbackCompletely())
103
+	} else {
104
+		runJsonMode()
105
+	}
106
+}

Loading…
Cancel
Save