Skip to content

Commit 6467a86

Browse files
authored
Merge pull request #2610 from jsternberg/bake-metrics
metrics: add metrics for bake command
2 parents 71174c3 + 58571ff commit 6467a86

File tree

2 files changed

+114
-24
lines changed

2 files changed

+114
-24
lines changed

commands/bake.go

Lines changed: 100 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,16 @@ import (
44
"bytes"
55
"cmp"
66
"context"
7+
"crypto/sha256"
8+
"encoding/hex"
79
"encoding/json"
810
"fmt"
911
"io"
1012
"os"
1113
"slices"
14+
"sort"
1215
"strings"
16+
"sync"
1317
"text/tabwriter"
1418

1519
"github.com/containerd/console"
@@ -26,13 +30,15 @@ import (
2630
"github.com/docker/buildx/util/confutil"
2731
"github.com/docker/buildx/util/desktop"
2832
"github.com/docker/buildx/util/dockerutil"
33+
"github.com/docker/buildx/util/osutil"
2934
"github.com/docker/buildx/util/progress"
3035
"github.com/docker/buildx/util/tracing"
3136
"github.com/docker/cli/cli/command"
3237
"github.com/moby/buildkit/identity"
3338
"github.com/moby/buildkit/util/progress/progressui"
3439
"github.com/pkg/errors"
3540
"github.com/spf13/cobra"
41+
"go.opentelemetry.io/otel/attribute"
3642
)
3743

3844
type bakeOptions struct {
@@ -52,6 +58,8 @@ type bakeOptions struct {
5258
}
5359

5460
func runBake(ctx context.Context, dockerCli command.Cli, targets []string, in bakeOptions, cFlags commonFlags) (err error) {
61+
mp := dockerCli.MeterProvider()
62+
5563
ctx, end, err := tracing.TraceCurrentCommand(ctx, "bake")
5664
if err != nil {
5765
return err
@@ -60,22 +68,7 @@ func runBake(ctx context.Context, dockerCli command.Cli, targets []string, in ba
6068
end(err)
6169
}()
6270

63-
var url string
64-
cmdContext := "cwd://"
65-
66-
if len(targets) > 0 {
67-
if build.IsRemoteURL(targets[0]) {
68-
url = targets[0]
69-
targets = targets[1:]
70-
if len(targets) > 0 {
71-
if build.IsRemoteURL(targets[0]) {
72-
cmdContext = targets[0]
73-
targets = targets[1:]
74-
}
75-
}
76-
}
77-
}
78-
71+
url, cmdContext, targets := bakeArgs(targets)
7972
if len(targets) == 0 {
8073
targets = []string{"default"}
8174
}
@@ -116,6 +109,7 @@ func runBake(ctx context.Context, dockerCli command.Cli, targets []string, in ba
116109
var progressConsoleDesc, progressTextDesc string
117110

118111
// instance only needed for reading remote bake files or building
112+
var driverType string
119113
if url != "" || !in.printOnly {
120114
b, err := builder.New(dockerCli,
121115
builder.WithName(in.builder),
@@ -133,17 +127,20 @@ func runBake(ctx context.Context, dockerCli command.Cli, targets []string, in ba
133127
}
134128
progressConsoleDesc = fmt.Sprintf("%s:%s", b.Driver, b.Name)
135129
progressTextDesc = fmt.Sprintf("building with %q instance using %s driver", b.Name, b.Driver)
130+
driverType = b.Driver
136131
}
137132

138133
var term bool
139134
if _, err := console.ConsoleFromFile(os.Stderr); err == nil {
140135
term = true
141136
}
137+
attributes := bakeMetricAttributes(dockerCli, driverType, url, cmdContext, targets, &in)
142138

143139
progressMode := progressui.DisplayMode(cFlags.progress)
144140
var printer *progress.Printer
145141
printer, err = progress.NewPrinter(ctx2, os.Stderr, progressMode,
146142
progress.WithDesc(progressTextDesc, progressConsoleDesc),
143+
progress.WithMetrics(mp, attributes),
147144
progress.WithOnClose(func() {
148145
printWarnings(os.Stderr, printer.Warnings(), progressMode)
149146
}),
@@ -241,12 +238,18 @@ func runBake(ctx context.Context, dockerCli command.Cli, targets []string, in ba
241238
return err
242239
}
243240

241+
done := timeBuildCommand(mp, attributes)
244242
resp, retErr := build.Build(ctx, nodes, bo, dockerutil.NewClient(dockerCli), confutil.ConfigDir(dockerCli), printer)
245243
if err := printer.Wait(); retErr == nil {
246244
retErr = err
247245
}
248246
if retErr != nil {
249-
return wrapBuildError(retErr, true)
247+
err = wrapBuildError(retErr, true)
248+
}
249+
done(err)
250+
251+
if err != nil {
252+
return err
250253
}
251254

252255
if progressMode != progressui.QuietMode && progressMode != progressui.RawJSONMode {
@@ -268,7 +271,7 @@ func runBake(ctx context.Context, dockerCli command.Cli, targets []string, in ba
268271
}
269272

270273
var callFormatJSON bool
271-
var jsonResults = map[string]map[string]any{}
274+
jsonResults := map[string]map[string]any{}
272275
if callFunc != nil {
273276
callFormatJSON = callFunc.Format == "json"
274277
}
@@ -456,6 +459,21 @@ func saveLocalStateGroup(dockerCli command.Cli, in bakeOptions, targets []string
456459
})
457460
}
458461

462+
// bakeArgs will retrieve the remote url, command context, and targets
463+
// from the command line arguments.
464+
func bakeArgs(args []string) (url, cmdContext string, targets []string) {
465+
cmdContext, targets = "cwd://", args
466+
if len(targets) == 0 || !build.IsRemoteURL(targets[0]) {
467+
return url, cmdContext, targets
468+
}
469+
url, targets = targets[0], targets[1:]
470+
if len(targets) == 0 || !build.IsRemoteURL(targets[0]) {
471+
return url, cmdContext, targets
472+
}
473+
cmdContext, targets = targets[0], targets[1:]
474+
return url, cmdContext, targets
475+
}
476+
459477
func readBakeFiles(ctx context.Context, nodes []builder.Node, url string, names []string, stdin io.Reader, pw progress.Writer) (files []bake.File, inp *bake.Input, err error) {
460478
var lnames []string // local
461479
var rnames []string // remote
@@ -570,3 +588,67 @@ func printTargetList(w io.Writer, cfg *bake.Config) error {
570588

571589
return nil
572590
}
591+
592+
func bakeMetricAttributes(dockerCli command.Cli, driverType, url, cmdContext string, targets []string, options *bakeOptions) attribute.Set {
593+
return attribute.NewSet(
594+
commandNameAttribute.String("bake"),
595+
attribute.Stringer(string(commandOptionsHash), &bakeOptionsHash{
596+
bakeOptions: options,
597+
configDir: confutil.ConfigDir(dockerCli),
598+
url: url,
599+
cmdContext: cmdContext,
600+
targets: targets,
601+
}),
602+
driverNameAttribute.String(options.builder),
603+
driverTypeAttribute.String(driverType),
604+
)
605+
}
606+
607+
type bakeOptionsHash struct {
608+
*bakeOptions
609+
configDir string
610+
url string
611+
cmdContext string
612+
targets []string
613+
result string
614+
resultOnce sync.Once
615+
}
616+
617+
func (o *bakeOptionsHash) String() string {
618+
o.resultOnce.Do(func() {
619+
url := o.url
620+
cmdContext := o.cmdContext
621+
if cmdContext == "cwd://" {
622+
// Resolve the directory if the cmdContext is the current working directory.
623+
cmdContext = osutil.GetWd()
624+
}
625+
626+
// Sort the inputs for files and targets since the ordering
627+
// doesn't matter, but avoid modifying the original slice.
628+
files := immutableSort(o.files)
629+
targets := immutableSort(o.targets)
630+
631+
joinedFiles := strings.Join(files, ",")
632+
joinedTargets := strings.Join(targets, ",")
633+
salt := confutil.TryNodeIdentifier(o.configDir)
634+
635+
h := sha256.New()
636+
for _, s := range []string{url, cmdContext, joinedFiles, joinedTargets, salt} {
637+
_, _ = io.WriteString(h, s)
638+
h.Write([]byte{0})
639+
}
640+
o.result = hex.EncodeToString(h.Sum(nil))
641+
})
642+
return o.result
643+
}
644+
645+
// immutableSort will sort the entries in s without modifying the original slice.
646+
func immutableSort(s []string) []string {
647+
if !sort.StringsAreSorted(s) {
648+
cpy := make([]string, len(s))
649+
copy(cpy, s)
650+
sort.Strings(cpy)
651+
return cpy
652+
}
653+
return s
654+
}

commands/build.go

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -224,15 +224,22 @@ func (o *buildOptions) toDisplayMode() (progressui.DisplayMode, error) {
224224
return progress, nil
225225
}
226226

227-
func buildMetricAttributes(dockerCli command.Cli, b *builder.Builder, options *buildOptions) attribute.Set {
227+
const (
228+
commandNameAttribute = attribute.Key("command.name")
229+
commandOptionsHash = attribute.Key("command.options.hash")
230+
driverNameAttribute = attribute.Key("driver.name")
231+
driverTypeAttribute = attribute.Key("driver.type")
232+
)
233+
234+
func buildMetricAttributes(dockerCli command.Cli, driverType string, options *buildOptions) attribute.Set {
228235
return attribute.NewSet(
229-
attribute.String("command.name", "build"),
230-
attribute.Stringer("command.options.hash", &buildOptionsHash{
236+
commandNameAttribute.String("build"),
237+
attribute.Stringer(string(commandOptionsHash), &buildOptionsHash{
231238
buildOptions: options,
232239
configDir: confutil.ConfigDir(dockerCli),
233240
}),
234-
attribute.String("driver.name", options.builder),
235-
attribute.String("driver.type", b.Driver),
241+
driverNameAttribute.String(options.builder),
242+
driverTypeAttribute.String(driverType),
236243
)
237244
}
238245

@@ -308,12 +315,13 @@ func runBuild(ctx context.Context, dockerCli command.Cli, options buildOptions)
308315
if err != nil {
309316
return err
310317
}
318+
driverType := b.Driver
311319

312320
var term bool
313321
if _, err := console.ConsoleFromFile(os.Stderr); err == nil {
314322
term = true
315323
}
316-
attributes := buildMetricAttributes(dockerCli, b, &options)
324+
attributes := buildMetricAttributes(dockerCli, driverType, &options)
317325

318326
ctx2, cancel := context.WithCancel(context.TODO())
319327
defer cancel()

0 commit comments

Comments
 (0)