-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathmain.go
146 lines (129 loc) · 2.96 KB
/
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
package main
import (
"encoding/json"
"fmt"
"io"
"os"
"path/filepath"
"strings"
txttpl "text/template"
"time"
"github.com/spf13/cobra"
)
func main() {
err := run(os.Args[1:]...)
if err != nil {
fmt.Fprintf(os.Stderr, "actiongraph: %s\n", err)
os.Exit(1)
}
}
func run(args ...string) error {
prog := &cobra.Command{
Use: "actiongraph",
SilenceUsage: true,
SilenceErrors: true,
}
prog.PersistentFlags().StringP("file", "f", "-", "JSON file to read (use - for stdin)")
prog.MarkFlagRequired("file")
prog.RegisterFlagCompletionFunc("file", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return []string{"json"}, cobra.ShellCompDirectiveFilterFileExt
})
addTopCommand(prog)
addTreeCommand(prog)
addTypesCommand(prog)
addGraphCommand(prog)
prog.AddGroup(&cobra.Group{
ID: "actiongraph",
Title: "Actiongraph:",
})
prog.SetArgs(args)
return prog.Execute()
}
type options struct {
stdin io.Reader
stdout io.Writer
args []string
funcs txttpl.FuncMap
actions []action
total time.Duration
}
func loadOptions(cmd *cobra.Command) (*options, error) {
opt := options{
stdin: cmd.InOrStdin(),
stdout: cmd.OutOrStdout(),
args: cmd.Flags().Args(),
funcs: txttpl.FuncMap{
"base": filepath.Base,
"dir": filepath.Dir,
"seconds": func(d time.Duration) string {
return fmt.Sprintf("%.3fs", d.Seconds())
},
"percent": func(v float64) string {
return fmt.Sprintf("%.2f%%", v)
},
"right": func(n int, s string) string {
if len(s) > n {
return s
}
return strings.Repeat(" ", n-len(s)) + s
},
},
}
// Open the actiongraph JSON file.
fn, err := cmd.Flags().GetString("file")
if err != nil {
return nil, err
}
f, err := openFile(fn)
if err != nil {
return nil, err
}
defer f.Close()
// Decode the actions.
if err := json.NewDecoder(f).Decode(&opt.actions); err != nil {
return nil, fmt.Errorf("decoding input: %w", err)
}
// A few top-level calculations.
for i := range opt.actions {
// TODO: Flag to look at CmdReal/CmdUser instead? We can use the Cmd
// field being non-null to differentiate between cached and
// non-cached steps, too.
d := opt.actions[i].TimeDone.Sub(opt.actions[i].TimeStart)
opt.actions[i].Duration = d
opt.total += d
}
for i := range opt.actions {
opt.actions[i].Percent = 100 * float64(opt.actions[i].Duration) / float64(opt.total)
}
return &opt, nil
}
func openFile(path string) (*os.File, error) {
switch path {
case "", "-", "/dev/stdin", "/dev/fd/0":
return os.Stdin, nil
default:
return os.Open(path)
}
}
type action struct {
ID int
Mode string
Package string
Deps []int
Objdir string
Target string
Priority int
Built string
BuildID string
TimeReady time.Time
TimeStart time.Time
TimeDone time.Time
Cmd any
ActionID string
CmdReal int
CmdUser int64
CmdSys int
NeedBuild bool
Duration time.Duration
Percent float64
}