Skip to content

Commit

Permalink
feat(internal): Introduce 'metrics' command for instances
Browse files Browse the repository at this point in the history
Signed-off-by: Cezar Craciunoiu <cezar.craciunoiu@unikraft.io>
  • Loading branch information
craciunoiuc committed Aug 30, 2024
1 parent bbc12f3 commit 069b4e7
Show file tree
Hide file tree
Showing 3 changed files with 188 additions and 0 deletions.
2 changes: 2 additions & 0 deletions internal/cli/kraft/cloud/instance/instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"kraftkit.sh/internal/cli/kraft/cloud/instance/get"
"kraftkit.sh/internal/cli/kraft/cloud/instance/list"
"kraftkit.sh/internal/cli/kraft/cloud/instance/logs"
"kraftkit.sh/internal/cli/kraft/cloud/instance/metrics"
"kraftkit.sh/internal/cli/kraft/cloud/instance/remove"
"kraftkit.sh/internal/cli/kraft/cloud/instance/start"
"kraftkit.sh/internal/cli/kraft/cloud/instance/stop"
Expand All @@ -42,6 +43,7 @@ func NewCmd() *cobra.Command {
cmd.AddCommand(create.NewCmd())
cmd.AddCommand(list.NewCmd())
cmd.AddCommand(logs.NewCmd())
cmd.AddCommand(metrics.NewCmd())
cmd.AddCommand(remove.NewCmd())
cmd.AddCommand(start.NewCmd())
cmd.AddCommand(get.NewCmd())
Expand Down
83 changes: 83 additions & 0 deletions internal/cli/kraft/cloud/instance/metrics/metrics.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2024, Unikraft GmbH and The KraftKit Authors.
// Licensed under the BSD-3-Clause License (the "License").
// You may not use this file except in compliance with the License.

package metrics

import (
"context"
"fmt"

"github.com/MakeNowJust/heredoc"
"github.com/spf13/cobra"

kraftcloud "sdk.kraft.cloud"

"kraftkit.sh/cmdfactory"
"kraftkit.sh/config"
"kraftkit.sh/internal/cli/kraft/cloud/utils"
)

type MetricsOptions struct {
Auth *config.AuthConfig `noattribute:"true"`
Client kraftcloud.KraftCloud `noattribute:"true"`
Metro string `noattribute:"true"`
Token string `noattribute:"true"`
Output string `long:"output" short:"o" usage:"Set output format. Options: table,yaml,json,list" default:"list"`
}

func NewCmd() *cobra.Command {
cmd, err := cmdfactory.New(&MetricsOptions{}, cobra.Command{
Short: "Return metrics for instances",
Use: "metrics [FLAGS] [UUID|NAME [UUID|NAME]...]",
Aliases: []string{"metric", "m", "meter"},
Args: cobra.MinimumNArgs(1),
Example: heredoc.Doc(`
# Return metrics for an instance by UUID
$ kraft cloud instance metrics fd1684ea-7970-4994-92d6-61dcc7905f2b
# Return metrics for an instance by name
$ kraft cloud instance metrics my-instance-431342
`),
Annotations: map[string]string{
cmdfactory.AnnotationHelpGroup: "kraftcloud-instance",
},
})
if err != nil {
panic(err)
}

return cmd
}

func (opts *MetricsOptions) Pre(cmd *cobra.Command, _ []string) error {
err := utils.PopulateMetroToken(cmd, &opts.Metro, &opts.Token)
if err != nil {
return fmt.Errorf("could not populate metro and token: %w", err)
}

if !utils.IsValidOutputFormat(opts.Output) {
return fmt.Errorf("invalid output format: %s", opts.Output)
}

return nil
}

func (opts *MetricsOptions) Run(ctx context.Context, args []string) error {
auth, err := config.GetKraftCloudAuthConfig(ctx, opts.Token)
if err != nil {
return fmt.Errorf("could not retrieve credentials: %w", err)
}

client := kraftcloud.NewInstancesClient(
kraftcloud.WithToken(config.GetKraftCloudTokenAuthConfig(*auth)),
)

resp, err := client.WithMetro(opts.Metro).Metrics(ctx, args...)
if err != nil {
return fmt.Errorf("could not get instance %s: %w", args, err)
}

return utils.PrintMetrics(ctx, opts.Output, *resp)
}
103 changes: 103 additions & 0 deletions internal/cli/kraft/cloud/utils/print.go
Original file line number Diff line number Diff line change
Expand Up @@ -1070,6 +1070,109 @@ func PrintCertificates(ctx context.Context, format string, resp kcclient.Service
return table.Render(iostreams.G(ctx).Out)
}

// PrintMetrics pretty-prints the provided set of instances metrics or returns
// an error if unable to send to stdout via the provided context.
func PrintMetrics(ctx context.Context, format string, resp kcclient.ServiceResponse[kcinstances.MetricsResponseItem]) error {
if format == "raw" {
printRaw(ctx, resp)
return nil
}

metrics, err := resp.AllOrErr()
if err != nil {
return err
}

if err := iostreams.G(ctx).StartPager(); err != nil {
log.G(ctx).Errorf("error starting pager: %v", err)
}

defer iostreams.G(ctx).StopPager()

cs := iostreams.G(ctx).ColorScheme()
table, err := tableprinter.NewTablePrinter(ctx,
tableprinter.WithMaxWidth(iostreams.G(ctx).TerminalWidth()),
tableprinter.WithOutputFormatFromString(format),
)
if err != nil {
return err
}

// Header row
if format != "table" {
table.AddField("UUID", cs.Bold)
}
table.AddField("NAME", cs.Bold)
table.AddField("RSS", cs.Bold)
table.AddField("CPU TIME", cs.Bold)
if format != "table" {
table.AddField("RX SIZE", cs.Bold)
table.AddField("RX PACKETS", cs.Bold)
table.AddField("TX SIZE", cs.Bold)
table.AddField("TX PACKETS", cs.Bold)
table.AddField("CONNECTIONS", cs.Bold)
table.AddField("REQUESTS", cs.Bold)
table.AddField("QUEUED", cs.Bold)
}
table.AddField("TOTAL", cs.Bold)
table.EndRow()

if config.G[config.KraftKit](ctx).NoColor {
instanceStateColor = instanceStateColorNil
}

for _, metric := range metrics {
if metric.Message != "" {
// Header row
if format != "table" {
table.AddField(metric.UUID, nil)
}
table.AddField(metric.Name, nil)
table.AddField("", nil) // RSS
table.AddField("", nil) // CPU TIME
if format != "table" {
table.AddField("", nil) // RX SIZE
table.AddField("", nil) // RX PACKETS
table.AddField("", nil) // TX SIZE
table.AddField("", nil) // TX PACKETS
table.AddField("", nil) // CONNECTIONS
table.AddField("", nil) // REQUESTS
table.AddField("", cs.Bold) // QUEUED
}
table.AddField("", cs.Bold) // TOTAL
table.EndRow()

continue
}
if format != "table" {
table.AddField(metric.UUID, nil)
}

table.AddField(metric.Name, nil)
table.AddField(humanize.IBytes(metric.RSS), nil)

duration, err := time.ParseDuration(fmt.Sprintf("%dms", metric.CPUTimeMs))
if err != nil {
return fmt.Errorf("could not parse CPU time for '%s': %w", metric.UUID, err)
}
table.AddField(duration.String(), nil)

if format != "table" {
table.AddField(humanize.IBytes(metric.RxBytes), nil)
table.AddField(fmt.Sprintf("%d", metric.RxPackets), nil)
table.AddField(humanize.IBytes(metric.TxBytes), nil)
table.AddField(fmt.Sprintf("%d", metric.TxPackets), nil)
table.AddField(fmt.Sprintf("%d", metric.Connections), nil)
table.AddField(fmt.Sprintf("%d", metric.Requests), nil)
table.AddField(fmt.Sprintf("%d", metric.Queued), nil)
}
table.AddField(fmt.Sprintf("%d", metric.Total), nil)
table.EndRow()
}

return table.Render(iostreams.G(ctx).Out)
}

// PrettyPrintInstance outputs a single instance and information about it.
func PrettyPrintInstance(ctx context.Context, instance kcinstances.GetResponseItem, service *kcservices.GetResponseItem, autoStart bool) {
out := iostreams.G(ctx).Out
Expand Down

0 comments on commit 069b4e7

Please sign in to comment.