From 42bfec50280657efbd983bc2da78dd81f0225966 Mon Sep 17 00:00:00 2001 From: Tonis Tiigi Date: Mon, 17 Jul 2017 14:24:19 -0700 Subject: [PATCH] buildctl: add dot output for visualizing llb usage: buildctl debug dump-llb --dot | dot -Tsvg Signed-off-by: Tonis Tiigi --- cmd/buildctl/debug/dumpllb.go | 53 ++++++++++++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/cmd/buildctl/debug/dumpllb.go b/cmd/buildctl/debug/dumpllb.go index a89719ec..275f75cf 100644 --- a/cmd/buildctl/debug/dumpllb.go +++ b/cmd/buildctl/debug/dumpllb.go @@ -2,8 +2,10 @@ package debug import ( "encoding/json" + "fmt" "io" "os" + "strings" "github.com/moby/buildkit/client/llb" "github.com/moby/buildkit/solver/pb" @@ -17,6 +19,12 @@ var DumpLLBCommand = cli.Command{ Usage: "dump LLB in human-readable format. LLB can be also passed via stdin. This command does not require the daemon to be running.", ArgsUsage: "", Action: dumpLLB, + Flags: []cli.Flag{ + cli.BoolFlag{ + Name: "dot", + Usage: "Output dot format", + }, + }, } func dumpLLB(clicontext *cli.Context) error { @@ -35,10 +43,14 @@ func dumpLLB(clicontext *cli.Context) error { if err != nil { return err } - enc := json.NewEncoder(os.Stdout) - for _, op := range ops { - if err := enc.Encode(op); err != nil { - return err + if clicontext.Bool("dot") { + writeDot(ops, os.Stdout) + } else { + enc := json.NewEncoder(os.Stdout) + for _, op := range ops { + if err := enc.Encode(op); err != nil { + return err + } } } return nil @@ -65,3 +77,36 @@ func loadLLB(r io.Reader) ([]llbOp, error) { } return ops, nil } + +func writeDot(ops []llbOp, w io.Writer) { + fmt.Fprintln(w, "digraph {") + defer fmt.Fprintln(w, "}") + for _, op := range ops { + name, shape := attr(op.Digest, op.Op) + fmt.Fprintf(w, " \"%s\" [label=\"%s\" shape=\"%s\"];\n", op.Digest, name, shape) + } + for _, op := range ops { + for i, inp := range op.Op.Inputs { + label := "" + if eo, ok := op.Op.Op.(*pb.Op_Exec); ok { + for _, m := range eo.Exec.Mounts { + if int(m.Input) == i && m.Dest != "/" { + label = m.Dest + } + } + } + fmt.Fprintf(w, " \"%s\" -> \"%s\" [label=\"%s\"];\n", inp.Digest, op.Digest, label) + } + } +} + +func attr(dgst digest.Digest, op pb.Op) (string, string) { + switch op := op.Op.(type) { + case *pb.Op_Source: + return op.Source.Identifier, "ellipse" + case *pb.Op_Exec: + return strings.Join(op.Exec.Meta.Args, " "), "box" + default: + return dgst.String(), "plaintext" + } +}