Skip to content
This repository has been archived by the owner on Nov 27, 2023. It is now read-only.

Commit

Permalink
metrics: initial logic for command execution duration
Browse files Browse the repository at this point in the history
Add a timer around command invocations to be reported with metrics.
This isn't actually sent anywhere currently, as it's meant for
evented data which is forthcoming. (We could report it with the
current events, but it's not clear that there's any value in doing
so.)
  • Loading branch information
milas committed Feb 6, 2023
1 parent 342e440 commit 3f93bb9
Show file tree
Hide file tree
Showing 5 changed files with 35 additions and 18 deletions.
35 changes: 23 additions & 12 deletions cli/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,10 +253,13 @@ func main() {

root.AddCommand(command)

if err = root.ExecuteContext(ctx); err != nil {
handleError(ctx, err, ctype, currentContext, cc, root)
start := time.Now()
err = root.ExecuteContext(ctx)
duration := time.Since(start)
if err != nil {
handleError(ctx, err, ctype, currentContext, cc, root, duration)
}
metricsClient.Track(ctype, os.Args[1:], metrics.SuccessStatus)
metricsClient.Track(ctype, os.Args[1:], metrics.SuccessStatus, duration)
}

func customizeCliForACI(command *cobra.Command, proxy *api.ServiceProxy) {
Expand All @@ -275,33 +278,41 @@ func customizeCliForACI(command *cobra.Command, proxy *api.ServiceProxy) {
}
}

func handleError(ctx context.Context, err error, ctype string, currentContext string, cc *store.DockerContext, root *cobra.Command) {
func handleError(
ctx context.Context,
err error,
ctype string,
currentContext string,
cc *store.DockerContext,
root *cobra.Command,
duration time.Duration,
) {
// if user canceled request, simply exit without any error message
if api.IsErrCanceled(err) || errors.Is(ctx.Err(), context.Canceled) {
metricsClient.Track(ctype, os.Args[1:], metrics.CanceledStatus)
metricsClient.Track(ctype, os.Args[1:], metrics.CanceledStatus, duration)
os.Exit(130)
}
if ctype == store.AwsContextType {
exit(currentContext, errors.Errorf(`%q context type has been renamed. Recreate the context by running:
$ docker context create %s <name>`, cc.Type(), store.EcsContextType), ctype)
$ docker context create %s <name>`, cc.Type(), store.EcsContextType), ctype, duration)
}

// Context should always be handled by new CLI
requiredCmd, _, _ := root.Find(os.Args[1:])
if requiredCmd != nil && isContextAgnosticCommand(requiredCmd) {
exit(currentContext, err, ctype)
exit(currentContext, err, ctype, duration)
}
mobycli.ExecIfDefaultCtxType(ctx, root)

checkIfUnknownCommandExistInDefaultContext(err, currentContext, ctype)

exit(currentContext, err, ctype)
exit(currentContext, err, ctype, duration)
}

func exit(ctx string, err error, ctype string) {
func exit(ctx string, err error, ctype string, duration time.Duration) {
if exit, ok := err.(cli.StatusError); ok {
// TODO(milas): shouldn't this use the exit code to determine status?
metricsClient.Track(ctype, os.Args[1:], metrics.SuccessStatus)
metricsClient.Track(ctype, os.Args[1:], metrics.SuccessStatus, duration)
os.Exit(exit.StatusCode)
}

Expand All @@ -316,7 +327,7 @@ func exit(ctx string, err error, ctype string) {
metricsStatus = metrics.CommandSyntaxFailure.MetricsStatus
exitCode = metrics.CommandSyntaxFailure.ExitCode
}
metricsClient.Track(ctype, os.Args[1:], metricsStatus)
metricsClient.Track(ctype, os.Args[1:], metricsStatus, duration)

if errors.Is(err, api.ErrLoginRequired) {
fmt.Fprintln(os.Stderr, err)
Expand Down Expand Up @@ -351,7 +362,7 @@ func checkIfUnknownCommandExistInDefaultContext(err error, currentContext string

if mobycli.IsDefaultContextCommand(dockerCommand) {
fmt.Fprintf(os.Stderr, "Command %q not available in current context (%s), you can use the \"default\" context to run this command\n", dockerCommand, currentContext)
metricsClient.Track(contextType, os.Args[1:], metrics.FailureStatus)
metricsClient.Track(contextType, os.Args[1:], metrics.FailureStatus, 0)
os.Exit(1)
}
}
Expand Down
2 changes: 1 addition & 1 deletion cli/metrics/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ type Client interface {
// Note that metric collection is best-effort, so any errors are ignored.
SendUsage(Command)
// Track creates an event for a command execution and reports it.
Track(context string, args []string, status string)
Track(context string, args []string, status string, duration time.Duration)
}

// NewClient returns a new metrics client that will send metrics using the
Expand Down
3 changes: 2 additions & 1 deletion cli/metrics/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,14 @@ package metrics
import (
"os"
"strings"
"time"

"github.com/docker/compose/v2/pkg/utils"

"github.com/docker/compose-cli/cli/metrics/metadata"
)

func (c *client) Track(context string, args []string, status string) {
func (c *client) Track(context string, args []string, status string, duration time.Duration) {
if isInvokedAsCliBackend() {
return
}
Expand Down
8 changes: 6 additions & 2 deletions cli/mobycli/exec.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"regexp"
"runtime"
"strings"
"time"

"github.com/google/shlex"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -76,20 +77,23 @@ func Exec(_ *cobra.Command) {
metricsClient.WithCliVersionFunc(func() string {
return CliVersion()
})
t := time.Now()
childExit := make(chan bool)
err := RunDocker(childExit, os.Args[1:]...)
childExit <- true
duration := time.Since(t)
if err != nil {
if exiterr, ok := err.(*exec.ExitError); ok {
exitCode := exiterr.ExitCode()
metricsClient.Track(
store.DefaultContextType,
os.Args[1:],
metrics.FailureCategoryFromExitCode(exitCode).MetricsStatus,
duration,
)
os.Exit(exitCode)
}
metricsClient.Track(store.DefaultContextType, os.Args[1:], metrics.FailureStatus)
metricsClient.Track(store.DefaultContextType, os.Args[1:], metrics.FailureStatus, duration)
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
Expand All @@ -98,7 +102,7 @@ func Exec(_ *cobra.Command) {
if command == "login" && !metrics.HasQuietFlag(commandArgs) {
displayPATSuggestMsg(commandArgs)
}
metricsClient.Track(store.DefaultContextType, os.Args[1:], metrics.SuccessStatus)
metricsClient.Track(store.DefaultContextType, os.Args[1:], metrics.SuccessStatus, duration)

os.Exit(0)
}
Expand Down
5 changes: 3 additions & 2 deletions cli/server/metrics_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"context"
"strings"
"testing"
"time"

"github.com/docker/compose/v2/pkg/api"
"github.com/stretchr/testify/mock"
Expand Down Expand Up @@ -130,6 +131,6 @@ func (s *mockMetricsClient) SendUsage(command metrics.Command) {
s.Called(command)
}

func (s *mockMetricsClient) Track(context string, args []string, status string) {
s.Called(context, args, status)
func (s *mockMetricsClient) Track(context string, args []string, status string, duration time.Duration) {
s.Called(context, args, status, duration)
}

0 comments on commit 3f93bb9

Please sign in to comment.