@@ -23,6 +23,7 @@ func makeResourceCmd() *cobra.Command {
23
23
}
24
24
25
25
func resourceTop ()* cobra.Command {
26
+ var group string
26
27
cmd := & cobra.Command {
27
28
Use :"top" ,
28
29
RunE :func (cmd * cobra.Command ,args []string )error {
@@ -34,69 +35,136 @@ func resourceTop() *cobra.Command {
34
35
35
36
// NOTE: it's not worth parrallelizing these calls yet given that this specific endpoint
36
37
// takes about 20x times longer than the other two
37
- envs ,err := client .Environments (ctx )
38
+ allEnvs ,err := client .Environments (ctx )
38
39
if err != nil {
39
40
return xerrors .Errorf ("get environments %w" ,err )
40
41
}
41
-
42
- userEnvs := make (map [string ][]coder.Environment )
43
- for _ ,e := range envs {
44
- userEnvs [e .UserID ]= append (userEnvs [e .UserID ],e )
42
+ // only include environments whose last status was "ON"
43
+ envs := make ([]coder.Environment ,0 )
44
+ for _ ,e := range allEnvs {
45
+ if e .LatestStat .ContainerStatus == coder .EnvironmentOn {
46
+ envs = append (envs ,e )
47
+ }
45
48
}
46
49
47
50
users ,err := client .Users (ctx )
48
51
if err != nil {
49
52
return xerrors .Errorf ("get users: %w" ,err )
50
53
}
51
54
52
- orgIDMap := make (map [string ]coder.Organization )
53
- orglist ,err := client .Organizations (ctx )
55
+ orgs ,err := client .Organizations (ctx )
54
56
if err != nil {
55
57
return xerrors .Errorf ("get organizations: %w" ,err )
56
58
}
57
- for _ ,o := range orglist {
58
- orgIDMap [o .ID ]= o
59
+
60
+ var groups []groupable
61
+ var labeler envLabeler
62
+ switch group {
63
+ case "user" :
64
+ userEnvs := make (map [string ][]coder.Environment ,len (users ))
65
+ for _ ,e := range envs {
66
+ userEnvs [e .UserID ]= append (userEnvs [e .UserID ],e )
67
+ }
68
+ for _ ,u := range users {
69
+ groups = append (groups ,userGrouping {user :u ,envs :userEnvs [u .ID ]})
70
+ }
71
+ orgIDMap := make (map [string ]coder.Organization )
72
+ for _ ,o := range orgs {
73
+ orgIDMap [o .ID ]= o
74
+ }
75
+ labeler = orgLabeler {orgIDMap }
76
+ case "org" :
77
+ orgEnvs := make (map [string ][]coder.Environment ,len (orgs ))
78
+ for _ ,e := range envs {
79
+ orgEnvs [e .OrganizationID ]= append (orgEnvs [e .OrganizationID ],e )
80
+ }
81
+ for _ ,o := range orgs {
82
+ groups = append (groups ,orgGrouping {org :o ,envs :orgEnvs [o .ID ]})
83
+ }
84
+ userIDMap := make (map [string ]coder.User )
85
+ for _ ,u := range users {
86
+ userIDMap [u .ID ]= u
87
+ }
88
+ labeler = userLabeler {userIDMap }
89
+ default :
90
+ return xerrors .Errorf ("unknown --group %q" ,group )
59
91
}
60
92
61
- printResourceTop (os .Stdout ,users , orgIDMap , userEnvs )
93
+ printResourceTop (os .Stdout ,groups , labeler )
62
94
return nil
63
95
},
64
96
}
97
+ cmd .Flags ().StringVar (& group ,"group" ,"user" ,"the grouping parameter (user|org)" )
65
98
66
99
return cmd
67
100
}
68
101
69
- func printResourceTop (writer io.Writer ,users []coder.User ,orgIDMap map [string ]coder.Organization ,userEnvs map [string ][]coder.Environment ) {
102
+ // groupable specifies a structure capable of being an aggregation group of environments (user, org, all)
103
+ type groupable interface {
104
+ header ()string
105
+ environments () []coder.Environment
106
+ }
107
+
108
+ type userGrouping struct {
109
+ user coder.User
110
+ envs []coder.Environment
111
+ }
112
+
113
+ func (u userGrouping )environments () []coder.Environment {
114
+ return u .envs
115
+ }
116
+
117
+ func (u userGrouping )header ()string {
118
+ return fmt .Sprintf ("%s\t (%s)" ,truncate (u .user .Name ,20 ,"..." ),u .user .Email )
119
+ }
120
+
121
+ type orgGrouping struct {
122
+ org coder.Organization
123
+ envs []coder.Environment
124
+ }
125
+
126
+ func (o orgGrouping )environments () []coder.Environment {
127
+ return o .envs
128
+ }
129
+
130
+ func (o orgGrouping )header ()string {
131
+ plural := "s"
132
+ if len (o .org .Members )< 2 {
133
+ plural = ""
134
+ }
135
+ return fmt .Sprintf ("%s\t (%v member%s)" ,truncate (o .org .Name ,20 ,"..." ),len (o .org .Members ),plural )
136
+ }
137
+
138
+ func printResourceTop (writer io.Writer ,groups []groupable ,labeler envLabeler ) {
70
139
tabwriter := tabwriter .NewWriter (writer ,0 ,0 ,4 ,' ' ,0 )
71
140
defer func () {_ = tabwriter .Flush () }()
72
141
73
- var userResources []aggregatedUser
74
- for _ ,u := range users {
142
+ var userResources []aggregatedResources
143
+ for _ ,group := range groups {
75
144
// truncate user names to ensure tabwriter doesn't push our entire table too far
76
- u .Name = truncate (u .Name ,20 ,"..." )
77
- userResources = append (userResources ,aggregatedUser {User :u ,resources :aggregateEnvResources (userEnvs [u .ID ])})
145
+ userResources = append (userResources ,aggregatedResources {groupable :group ,resources :aggregateEnvResources (group .environments ())})
78
146
}
79
147
sort .Slice (userResources ,func (i ,j int )bool {
80
148
return userResources [i ].cpuAllocation > userResources [j ].cpuAllocation
81
149
})
82
150
83
151
for _ ,u := range userResources {
84
- _ ,_ = fmt .Fprintf (tabwriter ,"%s\t (%s) \t %s " ,u .Name , u . Email ,u .resources )
152
+ _ ,_ = fmt .Fprintf (tabwriter ,"%s\t %s " ,u .header () ,u .resources )
85
153
if verbose {
86
- if len (userEnvs [ u . ID ] )> 0 {
154
+ if len (u . environments () )> 0 {
87
155
_ ,_ = fmt .Fprintf (tabwriter ,"\f " )
88
156
}
89
- for _ ,env := range userEnvs [ u . ID ] {
157
+ for _ ,env := range u . environments () {
90
158
_ ,_ = fmt .Fprintf (tabwriter ,"\t " )
91
- _ ,_ = fmt .Fprintln (tabwriter ,fmtEnvResources (env ,orgIDMap ))
159
+ _ ,_ = fmt .Fprintln (tabwriter ,fmtEnvResources (env ,labeler ))
92
160
}
93
161
}
94
162
_ ,_ = fmt .Fprint (tabwriter ,"\n " )
95
163
}
96
164
}
97
165
98
- type aggregatedUser struct {
99
- coder. User
166
+ type aggregatedResources struct {
167
+ groupable
100
168
resources
101
169
}
102
170
@@ -109,8 +177,28 @@ func resourcesFromEnv(env coder.Environment) resources {
109
177
}
110
178
}
111
179
112
- func fmtEnvResources (env coder.Environment ,orgs map [string ]coder.Organization )string {
113
- return fmt .Sprintf ("%s\t %s\t [org: %s]" ,env .Name ,resourcesFromEnv (env ),orgs [env .OrganizationID ].Name )
180
+ func fmtEnvResources (env coder.Environment ,labeler envLabeler )string {
181
+ return fmt .Sprintf ("%s\t %s\t %s" ,env .Name ,resourcesFromEnv (env ),labeler .label (env ))
182
+ }
183
+
184
+ type envLabeler interface {
185
+ label (coder.Environment )string
186
+ }
187
+
188
+ type orgLabeler struct {
189
+ orgMap map [string ]coder.Organization
190
+ }
191
+
192
+ func (o orgLabeler )label (e coder.Environment )string {
193
+ return fmt .Sprintf ("[org: %s]" ,o .orgMap [e .OrganizationID ].Name )
194
+ }
195
+
196
+ type userLabeler struct {
197
+ userMap map [string ]coder.User
198
+ }
199
+
200
+ func (u userLabeler )label (e coder.Environment )string {
201
+ return fmt .Sprintf ("[user: %s]" ,u .userMap [e .UserID ].Email )
114
202
}
115
203
116
204
func aggregateEnvResources (envs []coder.Environment )resources {