@@ -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 {
0 commit comments