Skip to content
This repository was archived by the owner on Aug 30, 2024. It is now read-only.

Commit d75e755

Browse files
committed
Abstract env resource grouping and label properly
1 parent 26b8ecc commit d75e755

File tree

1 file changed

+111
-23
lines changed

1 file changed

+111
-23
lines changed

internal/cmd/resourcemanager.go

+111-23
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ func makeResourceCmd() *cobra.Command {
2323
}
2424

2525
func resourceTop() *cobra.Command {
26+
var group string
2627
cmd := &cobra.Command{
2728
Use: "top",
2829
RunE: func(cmd *cobra.Command, args []string) error {
@@ -34,69 +35,136 @@ func resourceTop() *cobra.Command {
3435

3536
// NOTE: it's not worth parrallelizing these calls yet given that this specific endpoint
3637
// takes about 20x times longer than the other two
37-
envs, err := client.Environments(ctx)
38+
allEnvs, err := client.Environments(ctx)
3839
if err != nil {
3940
return xerrors.Errorf("get environments %w", err)
4041
}
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+
}
4548
}
4649

4750
users, err := client.Users(ctx)
4851
if err != nil {
4952
return xerrors.Errorf("get users: %w", err)
5053
}
5154

52-
orgIDMap := make(map[string]coder.Organization)
53-
orglist, err := client.Organizations(ctx)
55+
orgs, err := client.Organizations(ctx)
5456
if err != nil {
5557
return xerrors.Errorf("get organizations: %w", err)
5658
}
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)
5991
}
6092

61-
printResourceTop(os.Stdout, users, orgIDMap, userEnvs)
93+
printResourceTop(os.Stdout, groups, labeler)
6294
return nil
6395
},
6496
}
97+
cmd.Flags().StringVar(&group, "group", "user", "the grouping parameter (user|org)")
6598

6699
return cmd
67100
}
68101

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) {
70139
tabwriter := tabwriter.NewWriter(writer, 0, 0, 4, ' ', 0)
71140
defer func() { _ = tabwriter.Flush() }()
72141

73-
var userResources []aggregatedUser
74-
for _, u := range users {
142+
var userResources []aggregatedResources
143+
for _, group := range groups {
75144
// 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())})
78146
}
79147
sort.Slice(userResources, func(i, j int) bool {
80148
return userResources[i].cpuAllocation > userResources[j].cpuAllocation
81149
})
82150

83151
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)
85153
if verbose {
86-
if len(userEnvs[u.ID]) > 0 {
154+
if len(u.environments()) > 0 {
87155
_, _ = fmt.Fprintf(tabwriter, "\f")
88156
}
89-
for _, env := range userEnvs[u.ID] {
157+
for _, env := range u.environments() {
90158
_, _ = fmt.Fprintf(tabwriter, "\t")
91-
_, _ = fmt.Fprintln(tabwriter, fmtEnvResources(env, orgIDMap))
159+
_, _ = fmt.Fprintln(tabwriter, fmtEnvResources(env, labeler))
92160
}
93161
}
94162
_, _ = fmt.Fprint(tabwriter, "\n")
95163
}
96164
}
97165

98-
type aggregatedUser struct {
99-
coder.User
166+
type aggregatedResources struct {
167+
groupable
100168
resources
101169
}
102170

@@ -109,8 +177,28 @@ func resourcesFromEnv(env coder.Environment) resources {
109177
}
110178
}
111179

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)
114202
}
115203

116204
func aggregateEnvResources(envs []coder.Environment) resources {

0 commit comments

Comments
 (0)