From 6c7ee0a6cd7661ad83e6b11fa1e3ebbde242c8fa Mon Sep 17 00:00:00 2001 From: Andrea Falzetti Date: Fri, 10 Jun 2022 00:44:40 +0000 Subject: [PATCH] feat(gitpod-cli): add top cmd --- components/gitpod-cli/cmd/top.go | 80 +++++++++++++++++++ components/gitpod-cli/go.mod | 8 +- components/gitpod-cli/go.sum | 12 ++- .../pkg/supervisor-helper/status.go | 26 ++++++ components/gitpod-cli/pkg/utils/colors.go | 29 +++++++ 5 files changed, 147 insertions(+), 8 deletions(-) create mode 100644 components/gitpod-cli/cmd/top.go create mode 100644 components/gitpod-cli/pkg/supervisor-helper/status.go create mode 100644 components/gitpod-cli/pkg/utils/colors.go diff --git a/components/gitpod-cli/cmd/top.go b/components/gitpod-cli/cmd/top.go new file mode 100644 index 00000000000000..31d951c3a6d152 --- /dev/null +++ b/components/gitpod-cli/cmd/top.go @@ -0,0 +1,80 @@ +// Copyright (c) 2022 Gitpod GmbH. All rights reserved. +// Licensed under the GNU Affero General Public License (AGPL). +// See License-AGPL.txt in the project root for license information. + +package cmd + +import ( + "context" + "fmt" + "log" + "os" + "time" + + supervisor_helper "github.com/gitpod-io/gitpod/gitpod-cli/pkg/supervisor-helper" + supervisor "github.com/gitpod-io/gitpod/supervisor/api" + + "github.com/gitpod-io/gitpod/gitpod-cli/pkg/utils" + + "github.com/spf13/cobra" + + "github.com/olekukonko/tablewriter" +) + +var noColor bool + +func outputTable(workspaceResources *supervisor.ResourcesStatusResponse) { + table := tablewriter.NewWriter(os.Stdout) + table.SetHeader([]string{"CPU (millicores)", "Memory (bytes)"}) + table.SetBorders(tablewriter.Border{Left: true, Top: true, Right: true, Bottom: false}) + table.SetCenterSeparator("|") + + cpuFraction := int64((float64(workspaceResources.Cpu.Used) / float64(workspaceResources.Cpu.Limit)) * 100) + memFraction := int64((float64(workspaceResources.Memory.Used) / float64(workspaceResources.Memory.Limit)) * 100) + cpu := fmt.Sprintf("%dm/%dm (%d%%)", workspaceResources.Cpu.Used, workspaceResources.Cpu.Limit, cpuFraction) + memory := fmt.Sprintf("%dMi/%dMi (%d%%)\n", workspaceResources.Memory.Used/(1024*1024), workspaceResources.Memory.Limit/(1024*1024), memFraction) + + colors := []tablewriter.Colors{} + + if !noColor && utils.ColorsEnabled() { + cpuColor := getColor(cpuFraction) + memoryColor := getColor(memFraction) + colors = []tablewriter.Colors{{cpuColor}, {memoryColor}} + } + + table.Rich([]string{cpu, memory}, colors) + + table.Render() +} + +func getColor(value int64) int { + switch { + case value >= 85: + return tablewriter.FgRedColor + case value >= 65: + return tablewriter.FgYellowColor + default: + return tablewriter.FgHiGreenColor + } +} + +var topCmd = &cobra.Command{ + Use: "top", + Short: "Display usage of workspace resources (CPU and memory)", + Run: func(cmd *cobra.Command, args []string) { + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + + workspaceResources, err := supervisor_helper.GetWorkspaceResources(ctx) + if err != nil { + log.Fatalf("cannot get workspace resources: %s", err) + } + + outputTable(workspaceResources) + }, +} + +func init() { + topCmd.Flags().BoolVarP(&noColor, "no-color", "", false, "rewrites the host header of passing HTTP requests to localhost") + rootCmd.AddCommand(topCmd) +} diff --git a/components/gitpod-cli/go.mod b/components/gitpod-cli/go.mod index e9bf6799174d75..005f6f914f182d 100644 --- a/components/gitpod-cli/go.mod +++ b/components/gitpod-cli/go.mod @@ -19,8 +19,8 @@ require ( github.com/sirupsen/logrus v1.8.1 github.com/sourcegraph/jsonrpc2 v0.0.0-20200429184054-15c2290dcb37 github.com/spf13/cobra v1.1.3 - golang.org/x/sys v0.0.0-20210510120138-977fb7262007 - golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 + golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 + golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 google.golang.org/grpc v1.45.0 gopkg.in/alecthomas/kingpin.v3-unstable v3.0.0-20191105091915-95d230a53780 // indirect @@ -48,8 +48,8 @@ require ( github.com/spf13/pflag v1.0.5 // indirect github.com/tsenart/deadcode v0.0.0-20160724212837-210d2dc333e9 // indirect golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect - golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 // indirect - golang.org/x/text v0.3.5 // indirect + golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect + golang.org/x/text v0.3.6 // indirect golang.org/x/tools v0.1.3 // indirect google.golang.org/genproto v0.0.0-20210617175327-b9e0b3197ced // indirect google.golang.org/protobuf v1.28.0 // indirect diff --git a/components/gitpod-cli/go.sum b/components/gitpod-cli/go.sum index 78f63b6e0ff6cc..1ba72c0494ba82 100644 --- a/components/gitpod-cli/go.sum +++ b/components/gitpod-cli/go.sum @@ -395,8 +395,9 @@ golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4 h1:4nGaVu0QrbjT/AK2PRLuQfQuh6DJve+pELhqTdAj3x0= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -448,17 +449,20 @@ golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20220526004731-065cf7ba2467 h1:CBpWXWQpIRjzmkkA+M7q9Fqnwd2mZr3AFqexg8YTfoM= +golang.org/x/term v0.0.0-20220526004731-065cf7ba2467/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/components/gitpod-cli/pkg/supervisor-helper/status.go b/components/gitpod-cli/pkg/supervisor-helper/status.go new file mode 100644 index 00000000000000..0612881368e3d9 --- /dev/null +++ b/components/gitpod-cli/pkg/supervisor-helper/status.go @@ -0,0 +1,26 @@ +// Copyright (c) 2022 Gitpod GmbH. All rights reserved. +// Licensed under the GNU Affero General Public License (AGPL). +// See License-AGPL.txt in the project root for license information. + +package supervisor_helper + +import ( + "context" + + supervisor "github.com/gitpod-io/gitpod/supervisor/api" +) + +func GetWorkspaceResources(ctx context.Context) (*supervisor.ResourcesStatusResponse, error) { + conn, err := Dial(ctx) + if err != nil { + return nil, err + } + client := supervisor.NewStatusServiceClient(conn) + workspaceResources, workspaceResourcesError := client.ResourcesStatus(ctx, &supervisor.ResourcesStatuRequest{}) + + if workspaceResourcesError != nil { + return nil, workspaceResourcesError + } + + return workspaceResources, nil +} diff --git a/components/gitpod-cli/pkg/utils/colors.go b/components/gitpod-cli/pkg/utils/colors.go new file mode 100644 index 00000000000000..7594e46f68eac5 --- /dev/null +++ b/components/gitpod-cli/pkg/utils/colors.go @@ -0,0 +1,29 @@ +// Copyright (c) 2020 Gitpod GmbH. All rights reserved. +// Licensed under the GNU Affero General Public License (AGPL). +// See License-AGPL.txt in the project root for license information. + +package utils + +import ( + "os" + "syscall" + + "golang.org/x/term" +) + +func isInteractiveTerminal() bool { + return term.IsTerminal(syscall.Stdin) && term.IsTerminal(syscall.Stdout) +} + +func userRequestsNoColor() bool { + isDumbTerm := os.Getenv("TERM") == "dumb" + _, noColorPresent := os.LookupEnv("NO_COLOR") + _, gpNoColor := os.LookupEnv("GP_NO_COLOR") + + return isDumbTerm || noColorPresent || gpNoColor +} + +func ColorsEnabled() bool { + colorsDisabled := userRequestsNoColor() || !isInteractiveTerminal() + return !colorsDisabled +}