Skip to content
This repository was archived by the owner on Jul 18, 2025. It is now read-only.

Commit 16482c0

Browse files
committed
metrics: move helper code from Compose
Compose doesn't actually currently use this code, it's only used here. Moving it inline so that we can drop it from Compose in the future or make changes as needed without worrying about this as a dependency. Signed-off-by: Milas Bowman <[email protected]>
1 parent 2974b2a commit 16482c0

File tree

7 files changed

+108
-26
lines changed

7 files changed

+108
-26
lines changed

cli/main.go

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,7 @@ func main() {
256256
if err = root.ExecuteContext(ctx); err != nil {
257257
handleError(ctx, err, ctype, currentContext, cc, root)
258258
}
259-
metricsClient.Track(ctype, os.Args[1:], compose.SuccessStatus)
259+
metricsClient.Track(ctype, os.Args[1:], metrics.SuccessStatus)
260260
}
261261

262262
func customizeCliForACI(command *cobra.Command, proxy *api.ServiceProxy) {
@@ -278,7 +278,7 @@ func customizeCliForACI(command *cobra.Command, proxy *api.ServiceProxy) {
278278
func handleError(ctx context.Context, err error, ctype string, currentContext string, cc *store.DockerContext, root *cobra.Command) {
279279
// if user canceled request, simply exit without any error message
280280
if api.IsErrCanceled(err) || errors.Is(ctx.Err(), context.Canceled) {
281-
metricsClient.Track(ctype, os.Args[1:], compose.CanceledStatus)
281+
metricsClient.Track(ctype, os.Args[1:], metrics.CanceledStatus)
282282
os.Exit(130)
283283
}
284284
if ctype == store.AwsContextType {
@@ -300,20 +300,21 @@ $ docker context create %s <name>`, cc.Type(), store.EcsContextType), ctype)
300300

301301
func exit(ctx string, err error, ctype string) {
302302
if exit, ok := err.(cli.StatusError); ok {
303-
metricsClient.Track(ctype, os.Args[1:], compose.SuccessStatus)
303+
// TODO(milas): shouldn't this use the exit code to determine status?
304+
metricsClient.Track(ctype, os.Args[1:], metrics.SuccessStatus)
304305
os.Exit(exit.StatusCode)
305306
}
306307

307308
var composeErr compose.Error
308-
metricsStatus := compose.FailureStatus
309+
metricsStatus := metrics.FailureStatus
309310
exitCode := 1
310311
if errors.As(err, &composeErr) {
311312
metricsStatus = composeErr.GetMetricsFailureCategory().MetricsStatus
312313
exitCode = composeErr.GetMetricsFailureCategory().ExitCode
313314
}
314315
if strings.HasPrefix(err.Error(), "unknown shorthand flag:") || strings.HasPrefix(err.Error(), "unknown flag:") || strings.HasPrefix(err.Error(), "unknown docker command:") {
315-
metricsStatus = compose.CommandSyntaxFailure.MetricsStatus
316-
exitCode = compose.CommandSyntaxFailure.ExitCode
316+
metricsStatus = metrics.CommandSyntaxFailure.MetricsStatus
317+
exitCode = metrics.CommandSyntaxFailure.ExitCode
317318
}
318319
metricsClient.Track(ctype, os.Args[1:], metricsStatus)
319320

@@ -350,7 +351,7 @@ func checkIfUnknownCommandExistInDefaultContext(err error, currentContext string
350351

351352
if mobycli.IsDefaultContextCommand(dockerCommand) {
352353
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)
353-
metricsClient.Track(contextType, os.Args[1:], compose.FailureStatus)
354+
metricsClient.Track(contextType, os.Args[1:], metrics.FailureStatus)
354355
os.Exit(1)
355356
}
356357
}

cli/metrics/client.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,11 @@ type Client interface {
5757
// WithCliVersionFunc sets the docker cli version func
5858
// that returns the docker cli version (com.docker.cli)
5959
WithCliVersionFunc(f func() string)
60-
// Send sends the command to Docker Desktop. Note that the function doesn't
61-
// return anything, not even an error, this is because we don't really care
62-
// if the metrics were sent or not. We only fire and forget.
63-
Send(Command)
64-
// Track sends the tracking analytics to Docker Desktop
60+
// SendUsage sends the command to Docker Desktop.
61+
//
62+
// Note that metric collection is best-effort, so any errors are ignored.
63+
SendUsage(Command)
64+
// Track creates an event for a command execution and reports it.
6565
Track(context string, args []string, status string)
6666
}
6767

@@ -98,7 +98,7 @@ func (c *client) WithCliVersionFunc(f func() string) {
9898
c.cliversion.f = f
9999
}
100100

101-
func (c *client) Send(command Command) {
101+
func (c *client) SendUsage(command Command) {
102102
result := make(chan bool, 1)
103103
go func() {
104104
c.reporter.Heartbeat(command)

cli/metrics/event.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
/*
2+
Copyright 2020 Docker Compose CLI authors
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package metrics
18+
19+
// FailureCategory struct regrouping metrics failure status and specific exit code
20+
type FailureCategory struct {
21+
MetricsStatus string
22+
ExitCode int
23+
}
24+
25+
const (
26+
// APISource is sent for API metrics
27+
APISource = "api"
28+
// SuccessStatus command success
29+
SuccessStatus = "success"
30+
// FailureStatus command failure
31+
FailureStatus = "failure"
32+
// ComposeParseFailureStatus failure while parsing compose file
33+
ComposeParseFailureStatus = "failure-compose-parse"
34+
// FileNotFoundFailureStatus failure getting compose file
35+
FileNotFoundFailureStatus = "failure-file-not-found"
36+
// CommandSyntaxFailureStatus failure reading command
37+
CommandSyntaxFailureStatus = "failure-cmd-syntax"
38+
// BuildFailureStatus failure building imge
39+
BuildFailureStatus = "failure-build"
40+
// PullFailureStatus failure pulling imge
41+
PullFailureStatus = "failure-pull"
42+
// CanceledStatus command canceled
43+
CanceledStatus = "canceled"
44+
)
45+
46+
var (
47+
// FileNotFoundFailure failure for compose file not found
48+
FileNotFoundFailure = FailureCategory{MetricsStatus: FileNotFoundFailureStatus, ExitCode: 14}
49+
// ComposeParseFailure failure for composefile parse error
50+
ComposeParseFailure = FailureCategory{MetricsStatus: ComposeParseFailureStatus, ExitCode: 15}
51+
// CommandSyntaxFailure failure for command line syntax
52+
CommandSyntaxFailure = FailureCategory{MetricsStatus: CommandSyntaxFailureStatus, ExitCode: 16}
53+
// BuildFailure failure while building images.
54+
BuildFailure = FailureCategory{MetricsStatus: BuildFailureStatus, ExitCode: 17}
55+
// PullFailure failure while pulling image
56+
PullFailure = FailureCategory{MetricsStatus: PullFailureStatus, ExitCode: 18}
57+
)
58+
59+
// FailureCategoryFromExitCode retrieve FailureCategory based on command exit code
60+
func FailureCategoryFromExitCode(exitCode int) FailureCategory {
61+
switch exitCode {
62+
case 0:
63+
return FailureCategory{MetricsStatus: SuccessStatus, ExitCode: 0}
64+
case 14:
65+
return FileNotFoundFailure
66+
case 15:
67+
return ComposeParseFailure
68+
case 16:
69+
return CommandSyntaxFailure
70+
case 17:
71+
return BuildFailure
72+
case 18:
73+
return PullFailure
74+
case 130:
75+
return FailureCategory{MetricsStatus: CanceledStatus, ExitCode: exitCode}
76+
default:
77+
return FailureCategory{MetricsStatus: FailureStatus, ExitCode: exitCode}
78+
}
79+
}

cli/metrics/metrics.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ func (c *client) Track(context string, args []string, status string) {
3131
}
3232
command := GetCommand(args)
3333
if command != "" {
34-
c.Send(Command{
34+
c.SendUsage(Command{
3535
Command: command,
3636
Context: context,
3737
Source: c.getMetadata(CLISource, args),

cli/mobycli/exec.go

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ import (
2828
"runtime"
2929
"strings"
3030

31-
"github.com/docker/compose/v2/pkg/compose"
3231
"github.com/google/shlex"
3332
"github.com/spf13/cobra"
3433

@@ -72,7 +71,7 @@ func mustDelegateToMoby(ctxType string) bool {
7271
}
7372

7473
// Exec delegates to com.docker.cli if on moby context
75-
func Exec(root *cobra.Command) {
74+
func Exec(_ *cobra.Command) {
7675
metricsClient := metrics.NewDefaultClient()
7776
metricsClient.WithCliVersionFunc(func() string {
7877
return CliVersion()
@@ -83,10 +82,14 @@ func Exec(root *cobra.Command) {
8382
if err != nil {
8483
if exiterr, ok := err.(*exec.ExitError); ok {
8584
exitCode := exiterr.ExitCode()
86-
metricsClient.Track(store.DefaultContextType, os.Args[1:], compose.ByExitCode(exitCode).MetricsStatus)
85+
metricsClient.Track(
86+
store.DefaultContextType,
87+
os.Args[1:],
88+
metrics.FailureCategoryFromExitCode(exitCode).MetricsStatus,
89+
)
8790
os.Exit(exitCode)
8891
}
89-
metricsClient.Track(store.DefaultContextType, os.Args[1:], compose.FailureStatus)
92+
metricsClient.Track(store.DefaultContextType, os.Args[1:], metrics.FailureStatus)
9093
fmt.Fprintln(os.Stderr, err)
9194
os.Exit(1)
9295
}
@@ -95,7 +98,7 @@ func Exec(root *cobra.Command) {
9598
if command == "login" && !metrics.HasQuietFlag(commandArgs) {
9699
displayPATSuggestMsg(commandArgs)
97100
}
98-
metricsClient.Track(store.DefaultContextType, os.Args[1:], compose.SuccessStatus)
101+
metricsClient.Track(store.DefaultContextType, os.Args[1:], metrics.SuccessStatus)
99102

100103
os.Exit(0)
101104
}

cli/server/metrics.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ package server
1919
import (
2020
"context"
2121

22-
"github.com/docker/compose/v2/pkg/compose"
2322
"google.golang.org/grpc"
2423

2524
"github.com/docker/compose-cli/cli/metrics"
@@ -61,16 +60,16 @@ func metricsServerInterceptor(client metrics.Client) grpc.UnaryServerInterceptor
6160

6261
data, err := handler(ctx, req)
6362

64-
status := compose.SuccessStatus
63+
status := metrics.SuccessStatus
6564
if err != nil {
66-
status = compose.FailureStatus
65+
status = metrics.FailureStatus
6766
}
6867
command := methodMapping[info.FullMethod]
6968
if command != "" {
70-
client.Send(metrics.Command{
69+
client.SendUsage(metrics.Command{
7170
Command: command,
7271
Context: contextType,
73-
Source: compose.APISource,
72+
Source: metrics.APISource,
7473
Status: status,
7574
})
7675
}

cli/server/metrics_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ func TestAllMethodsHaveCorrespondingCliCommand(t *testing.T) {
6060

6161
func TestTrackSuccess(t *testing.T) {
6262
var mockMetrics = &mockMetricsClient{}
63-
mockMetrics.On("Send", metrics.Command{Command: "ps", Context: "aci", Status: "success", Source: "api"}).Return()
63+
mockMetrics.On("SendUsage", metrics.Command{Command: "ps", Context: "aci", Status: "success", Source: "api"}).Return()
6464
newClient := client.NewClient("aci", noopService{})
6565
interceptor := metricsServerInterceptor(mockMetrics)
6666

@@ -126,7 +126,7 @@ func (s *mockMetricsClient) WithCliVersionFunc(f func() string) {
126126
s.Called(f)
127127
}
128128

129-
func (s *mockMetricsClient) Send(command metrics.Command) {
129+
func (s *mockMetricsClient) SendUsage(command metrics.Command) {
130130
s.Called(command)
131131
}
132132

0 commit comments

Comments
 (0)