Skip to content

Commit ee359a3

Browse files
committed
OTel: add command.time metric to plugin commands
Signed-off-by: Laura Brehm <[email protected]> (cherry picked from commit f07834d)
1 parent 004e292 commit ee359a3

File tree

3 files changed

+41
-8
lines changed

3 files changed

+41
-8
lines changed

cli-plugins/plugin/plugin.go

+18
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,24 @@ func RunPlugin(dockerCli *command.DockerCli, plugin *cobra.Command, meta manager
5252
opts = append(opts, withPluginClientConn(plugin.Name()))
5353
}
5454
err = tcmd.Initialize(opts...)
55+
ogRunE := cmd.RunE
56+
if ogRunE == nil {
57+
ogRun := cmd.Run
58+
// necessary because error will always be nil here
59+
// see: https://github.com/golangci/golangci-lint/issues/1379
60+
//nolint:unparam
61+
ogRunE = func(cmd *cobra.Command, args []string) error {
62+
ogRun(cmd, args)
63+
return nil
64+
}
65+
cmd.Run = nil
66+
}
67+
cmd.RunE = func(cmd *cobra.Command, args []string) error {
68+
stopInstrumentation := dockerCli.StartInstrumentation(cmd)
69+
err := ogRunE(cmd, args)
70+
stopInstrumentation(err)
71+
return err
72+
}
5573
})
5674
return err
5775
}

cli/command/telemetry_utils.go

+22-7
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,7 @@ func BaseCommandAttributes(cmd *cobra.Command, streams Streams) []attribute.KeyV
2626
// Note: this should be the last func to wrap/modify the PersistentRunE/RunE funcs before command execution.
2727
//
2828
// can also be used for spans!
29-
func (cli *DockerCli) InstrumentCobraCommands(cmd *cobra.Command, mp metric.MeterProvider) {
30-
meter := getDefaultMeter(mp)
29+
func (cli *DockerCli) InstrumentCobraCommands(ctx context.Context, cmd *cobra.Command) {
3130
// If PersistentPreRunE is nil, make it execute PersistentPreRun and return nil by default
3231
ogPersistentPreRunE := cmd.PersistentPreRunE
3332
if ogPersistentPreRunE == nil {
@@ -55,19 +54,27 @@ func (cli *DockerCli) InstrumentCobraCommands(cmd *cobra.Command, mp metric.Mete
5554
}
5655
cmd.RunE = func(cmd *cobra.Command, args []string) error {
5756
// start the timer as the first step of every cobra command
58-
baseAttrs := BaseCommandAttributes(cmd, cli)
59-
stopCobraCmdTimer := startCobraCommandTimer(cmd, meter, baseAttrs)
57+
stopInstrumentation := cli.StartInstrumentation(cmd)
6058
cmdErr := ogRunE(cmd, args)
61-
stopCobraCmdTimer(cmdErr)
59+
stopInstrumentation(cmdErr)
6260
return cmdErr
6361
}
6462

6563
return ogPersistentPreRunE(cmd, args)
6664
}
6765
}
6866

69-
func startCobraCommandTimer(cmd *cobra.Command, meter metric.Meter, attrs []attribute.KeyValue) func(err error) {
70-
ctx := cmd.Context()
67+
// StartInstrumentation instruments CLI commands with the individual metrics and spans configured.
68+
// It's the main command OTel utility, and new command-related metrics should be added to it.
69+
// It should be called immediately before command execution, and returns a stopInstrumentation function
70+
// that must be called with the error resulting from the command execution.
71+
func (cli *DockerCli) StartInstrumentation(cmd *cobra.Command) (stopInstrumentation func(error)) {
72+
baseAttrs := BaseCommandAttributes(cmd, cli)
73+
return startCobraCommandTimer(cli.MeterProvider(), baseAttrs)
74+
}
75+
76+
func startCobraCommandTimer(mp metric.MeterProvider, attrs []attribute.KeyValue) func(err error) {
77+
meter := getDefaultMeter(mp)
7178
durationCounter, _ := meter.Float64Counter(
7279
"command.time",
7380
metric.WithDescription("Measures the duration of the cobra command"),
@@ -76,12 +83,20 @@ func startCobraCommandTimer(cmd *cobra.Command, meter metric.Meter, attrs []attr
7683
start := time.Now()
7784

7885
return func(err error) {
86+
// Use a new context for the export so that the command being cancelled
87+
// doesn't affect the metrics, and we get metrics for cancelled commands.
88+
ctx, cancel := context.WithTimeout(context.Background(), exportTimeout)
89+
defer cancel()
90+
7991
duration := float64(time.Since(start)) / float64(time.Millisecond)
8092
cmdStatusAttrs := attributesFromError(err)
8193
durationCounter.Add(ctx, duration,
8294
metric.WithAttributes(attrs...),
8395
metric.WithAttributes(cmdStatusAttrs...),
8496
)
97+
if mp, ok := mp.(MeterProvider); ok {
98+
mp.ForceFlush(ctx)
99+
}
85100
}
86101
}
87102

cmd/docker/docker.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ func runDocker(ctx context.Context, dockerCli *command.DockerCli) error {
314314
fmt.Fprint(dockerCli.Err(), "Warning: Unexpected OTEL error, metrics may not be flushed")
315315
}
316316

317-
dockerCli.InstrumentCobraCommands(cmd, mp)
317+
dockerCli.InstrumentCobraCommands(ctx, cmd)
318318

319319
var envs []string
320320
args, os.Args, envs, err = processAliases(dockerCli, cmd, args, os.Args)

0 commit comments

Comments
 (0)