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

Commit ce03114

Browse files
authored
Merge pull request #2225 from milas/metrics-duration
metrics: initial logic for command execution duration
2 parents 98291e0 + e7d8d05 commit ce03114

File tree

5 files changed

+113
-26
lines changed

5 files changed

+113
-26
lines changed

cli/main.go

+66-13
Original file line numberDiff line numberDiff line change
@@ -253,10 +253,20 @@ func main() {
253253

254254
root.AddCommand(command)
255255

256-
if err = root.ExecuteContext(ctx); err != nil {
257-
handleError(ctx, err, ctype, currentContext, cc, root)
256+
start := time.Now().UTC()
257+
err = root.ExecuteContext(ctx)
258+
duration := time.Since(start)
259+
if err != nil {
260+
handleError(ctx, err, ctype, currentContext, cc, root, start, duration)
258261
}
259-
metricsClient.Track(ctype, os.Args[1:], metrics.SuccessStatus)
262+
metricsClient.Track(
263+
metrics.CmdMeta{
264+
ContextType: ctype,
265+
Args: os.Args[1:],
266+
Status: metrics.SuccessStatus,
267+
Start: start,
268+
Duration: duration,
269+
})
260270
}
261271

262272
func customizeCliForACI(command *cobra.Command, proxy *api.ServiceProxy) {
@@ -275,33 +285,64 @@ func customizeCliForACI(command *cobra.Command, proxy *api.ServiceProxy) {
275285
}
276286
}
277287

278-
func handleError(ctx context.Context, err error, ctype string, currentContext string, cc *store.DockerContext, root *cobra.Command) {
288+
func handleError(
289+
ctx context.Context,
290+
err error,
291+
ctype string,
292+
currentContext string,
293+
cc *store.DockerContext,
294+
root *cobra.Command,
295+
start time.Time,
296+
duration time.Duration,
297+
) {
279298
// if user canceled request, simply exit without any error message
280299
if api.IsErrCanceled(err) || errors.Is(ctx.Err(), context.Canceled) {
281-
metricsClient.Track(ctype, os.Args[1:], metrics.CanceledStatus)
300+
metricsClient.Track(
301+
metrics.CmdMeta{
302+
ContextType: ctype,
303+
Args: os.Args[1:],
304+
Status: metrics.CanceledStatus,
305+
Start: start,
306+
Duration: duration,
307+
},
308+
)
282309
os.Exit(130)
283310
}
284311
if ctype == store.AwsContextType {
285-
exit(currentContext, errors.Errorf(`%q context type has been renamed. Recreate the context by running:
286-
$ docker context create %s <name>`, cc.Type(), store.EcsContextType), ctype)
312+
exit(
313+
currentContext,
314+
errors.Errorf(`%q context type has been renamed. Recreate the context by running:
315+
$ docker context create %s <name>`, cc.Type(), store.EcsContextType),
316+
ctype,
317+
start,
318+
duration,
319+
)
287320
}
288321

289322
// Context should always be handled by new CLI
290323
requiredCmd, _, _ := root.Find(os.Args[1:])
291324
if requiredCmd != nil && isContextAgnosticCommand(requiredCmd) {
292-
exit(currentContext, err, ctype)
325+
exit(currentContext, err, ctype, start, duration)
293326
}
294327
mobycli.ExecIfDefaultCtxType(ctx, root)
295328

296329
checkIfUnknownCommandExistInDefaultContext(err, currentContext, ctype)
297330

298-
exit(currentContext, err, ctype)
331+
exit(currentContext, err, ctype, start, duration)
299332
}
300333

301-
func exit(ctx string, err error, ctype string) {
334+
func exit(ctx string, err error, ctype string, start time.Time, duration time.Duration) {
302335
if exit, ok := err.(cli.StatusError); ok {
303336
// TODO(milas): shouldn't this use the exit code to determine status?
304-
metricsClient.Track(ctype, os.Args[1:], metrics.SuccessStatus)
337+
metricsClient.Track(
338+
metrics.CmdMeta{
339+
ContextType: ctype,
340+
Args: os.Args[1:],
341+
Status: metrics.SuccessStatus,
342+
Start: start,
343+
Duration: duration,
344+
},
345+
)
305346
os.Exit(exit.StatusCode)
306347
}
307348

@@ -316,7 +357,15 @@ func exit(ctx string, err error, ctype string) {
316357
metricsStatus = metrics.CommandSyntaxFailure.MetricsStatus
317358
exitCode = metrics.CommandSyntaxFailure.ExitCode
318359
}
319-
metricsClient.Track(ctype, os.Args[1:], metricsStatus)
360+
metricsClient.Track(
361+
metrics.CmdMeta{
362+
ContextType: ctype,
363+
Args: os.Args[1:],
364+
Status: metricsStatus,
365+
Start: start,
366+
Duration: duration,
367+
},
368+
)
320369

321370
if errors.Is(err, api.ErrLoginRequired) {
322371
fmt.Fprintln(os.Stderr, err)
@@ -351,7 +400,11 @@ func checkIfUnknownCommandExistInDefaultContext(err error, currentContext string
351400

352401
if mobycli.IsDefaultContextCommand(dockerCommand) {
353402
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)
354-
metricsClient.Track(contextType, os.Args[1:], metrics.FailureStatus)
403+
metricsClient.Track(metrics.CmdMeta{
404+
ContextType: contextType,
405+
Args: os.Args[1:],
406+
Status: metrics.FailureStatus,
407+
})
355408
os.Exit(1)
356409
}
357410
}

cli/metrics/client.go

+10-1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,15 @@ import (
2626
// specified file path.
2727
const EnvVarDebugMetricsPath = "DOCKER_METRICS_DEBUG_LOG"
2828

29+
type CmdMeta struct {
30+
ContextType string
31+
Args []string
32+
Status string
33+
ExitCode int
34+
Start time.Time
35+
Duration time.Duration
36+
}
37+
2938
type client struct {
3039
cliversion *cliversion
3140
reporter Reporter
@@ -62,7 +71,7 @@ type Client interface {
6271
// Note that metric collection is best-effort, so any errors are ignored.
6372
SendUsage(Command)
6473
// Track creates an event for a command execution and reports it.
65-
Track(context string, args []string, status string)
74+
Track(cmd CmdMeta)
6675
}
6776

6877
// NewClient returns a new metrics client that will send metrics using the

cli/metrics/metrics.go

+5-5
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,17 @@ import (
2525
"github.com/docker/compose-cli/cli/metrics/metadata"
2626
)
2727

28-
func (c *client) Track(context string, args []string, status string) {
28+
func (c *client) Track(cmd CmdMeta) {
2929
if isInvokedAsCliBackend() {
3030
return
3131
}
32-
command := GetCommand(args)
32+
command := GetCommand(cmd.Args)
3333
if command != "" {
3434
c.SendUsage(Command{
3535
Command: command,
36-
Context: context,
37-
Source: c.getMetadata(CLISource, args),
38-
Status: status,
36+
Context: cmd.ContextType,
37+
Source: c.getMetadata(CLISource, cmd.Args),
38+
Status: cmd.Status,
3939
})
4040
}
4141
}

cli/mobycli/exec.go

+30-5
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"regexp"
2828
"runtime"
2929
"strings"
30+
"time"
3031

3132
"github.com/google/shlex"
3233
"github.com/spf13/cobra"
@@ -76,20 +77,35 @@ func Exec(_ *cobra.Command) {
7677
metricsClient.WithCliVersionFunc(func() string {
7778
return CliVersion()
7879
})
80+
start := time.Now().UTC()
7981
childExit := make(chan bool)
8082
err := RunDocker(childExit, os.Args[1:]...)
8183
childExit <- true
84+
duration := time.Since(start)
8285
if err != nil {
8386
if exiterr, ok := err.(*exec.ExitError); ok {
8487
exitCode := exiterr.ExitCode()
8588
metricsClient.Track(
86-
store.DefaultContextType,
87-
os.Args[1:],
88-
metrics.FailureCategoryFromExitCode(exitCode).MetricsStatus,
89+
metrics.CmdMeta{
90+
ContextType: store.DefaultContextType,
91+
Args: os.Args[1:],
92+
Status: metrics.FailureCategoryFromExitCode(exitCode).MetricsStatus,
93+
ExitCode: exitCode,
94+
Start: start,
95+
Duration: duration,
96+
},
8997
)
9098
os.Exit(exitCode)
9199
}
92-
metricsClient.Track(store.DefaultContextType, os.Args[1:], metrics.FailureStatus)
100+
metricsClient.Track(
101+
metrics.CmdMeta{
102+
ContextType: store.DefaultContextType,
103+
Args: os.Args[1:],
104+
Status: metrics.FailureStatus,
105+
Start: start,
106+
Duration: duration,
107+
},
108+
)
93109
fmt.Fprintln(os.Stderr, err)
94110
os.Exit(1)
95111
}
@@ -98,7 +114,16 @@ func Exec(_ *cobra.Command) {
98114
if command == "login" && !metrics.HasQuietFlag(commandArgs) {
99115
displayPATSuggestMsg(commandArgs)
100116
}
101-
metricsClient.Track(store.DefaultContextType, os.Args[1:], metrics.SuccessStatus)
117+
metricsClient.Track(
118+
metrics.CmdMeta{
119+
ContextType: store.DefaultContextType,
120+
Args: os.Args[1:],
121+
Status: metrics.SuccessStatus,
122+
ExitCode: 0,
123+
Start: start,
124+
Duration: duration,
125+
},
126+
)
102127

103128
os.Exit(0)
104129
}

cli/server/metrics_test.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,6 @@ func (s *mockMetricsClient) SendUsage(command metrics.Command) {
130130
s.Called(command)
131131
}
132132

133-
func (s *mockMetricsClient) Track(context string, args []string, status string) {
134-
s.Called(context, args, status)
133+
func (s *mockMetricsClient) Track(cmd metrics.CmdMeta) {
134+
s.Called(cmd)
135135
}

0 commit comments

Comments
 (0)