Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: parallel generate #1937

Merged
merged 5 commits into from
Oct 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 31 additions & 25 deletions cmd/terramate/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ type cliSpec struct {

DisableCheckpoint bool `hidden:"true" optional:"true" default:"false" help:"Disable checkpoint checks for updates."`
DisableCheckpointSignature bool `hidden:"true" optional:"true" default:"false" help:"Disable checkpoint signature."`
CPUProfiling bool `hidden:"true" optional:"true" default:"false" help:"Create a CPU profile file when running"`

Create struct {
Path string `arg:"" optional:"" name:"path" predictor:"file" help:"Path of the new stack."`
Expand Down Expand Up @@ -151,6 +152,7 @@ type cliSpec struct {
} `cmd:"" help:"Run command in the stacks"`

Generate struct {
Parallel int `env:"TM_ARG_GENERATE_PARALLEL" short:"j" optional:"true" help:"Set the parallelism of code generation"`
DetailedExitCode bool `default:"false" help:"Return a detailed exit code: 0 nothing changed, 1 an error happened, 2 changes were made."`
} `cmd:"" help:"Run Code Generation in stacks."`

Expand Down Expand Up @@ -394,7 +396,7 @@ type cli struct {
stderr io.Writer
output out.O // Deprecated: use printer.Stdout/Stderr
exit bool
prj project
prj *project
httpClient http.Client
cloud cloudConfig
uimode UIMode
Expand Down Expand Up @@ -452,6 +454,7 @@ func newCLI(version string, args []string, stdin io.Reader, stdout, stderr io.Wr
)

ctx, err := parser.Parse(args)
// Note: err is checked later due to Kong workarounds in place.

if kongExit && kongExitStatus == 0 {
return &cli{exit: true}
Expand All @@ -469,6 +472,9 @@ func newCLI(version string, args []string, stdin io.Reader, stdout, stderr io.Wr
fatalWithDetailf(err, "parsing cli args %v", args)
}

// profiler is only started if Terramate is built with -tags profiler
startProfiler(&parsedArgs)

configureLogging(parsedArgs.LogLevel, parsedArgs.LogFmt,
parsedArgs.LogDestination, stdout, stderr)
// If we don't re-create the logger after configuring we get some
Expand Down Expand Up @@ -704,7 +710,9 @@ func (c *cli) run() {
c.setupSafeguards(c.parsedArgs.Run.runSafeguardsCliSpec)
c.runOnStacks()
case "generate":
c.generate()
exitCode := c.generate()
stopProfiler(c.parsedArgs)
os.Exit(exitCode)
case "experimental clone <srcdir> <destdir>":
c.cloneStack()
case "experimental trigger":
Expand Down Expand Up @@ -1112,7 +1120,7 @@ func (c *cli) cloneStack() {
c.generate()
}

func (c *cli) generate() {
func (c *cli) generate() int {
report, vendorReport := c.gencodeWithVendor()

c.output.MsgStdOut(report.Full())
Expand All @@ -1123,7 +1131,6 @@ func (c *cli) generate() {

if !vendorReport.IsEmpty() {
c.output.MsgStdOut(vendorReport.String())

}

if c.parsedArgs.Generate.DetailedExitCode {
Expand All @@ -1135,13 +1142,12 @@ func (c *cli) generate() {
if report.HasFailures() || vendorReport.HasFailures() {
exitCode = 1
}

os.Exit(exitCode)
return exitCode
}

// gencodeWithVendor will generate code for the whole project providing automatic
// vendoring of all tm_vendor calls.
func (c *cli) gencodeWithVendor() (generate.Report, download.Report) {
func (c *cli) gencodeWithVendor() (*generate.Report, download.Report) {
vendorProgressEvents := download.NewEventStream()
progressHandlerDone := c.handleVendorProgressEvents(vendorProgressEvents)

Expand All @@ -1154,25 +1160,25 @@ func (c *cli) gencodeWithVendor() (generate.Report, download.Report) {

mergedVendorReport := download.MergeVendorReports(vendorReports)

log.Debug().Msg("generating code")
log.Trace().Msg("generating code")

cwd := prj.PrjAbsPath(c.cfg().HostDir(), c.wd())
report := generate.Do(c.cfg(), cwd, c.vendorDir(), vendorRequestEvents)
report := generate.Do(c.cfg(), cwd, c.parsedArgs.Generate.Parallel, c.vendorDir(), vendorRequestEvents)

log.Debug().Msg("code generation finished, waiting for vendor requests to be handled")
log.Trace().Msg("code generation finished, waiting for vendor requests to be handled")

close(vendorRequestEvents)

log.Debug().Msg("waiting for vendor report merging")
log.Trace().Msg("waiting for vendor report merging")

vendorReport := <-mergedVendorReport

log.Debug().Msg("waiting for all progress events")
log.Trace().Msg("waiting for all progress events")

close(vendorProgressEvents)
<-progressHandlerDone

log.Debug().Msg("all handlers stopped, generating final report")
log.Trace().Msg("all handlers stopped, generating final report")

return report, vendorReport
}
Expand Down Expand Up @@ -1493,7 +1499,7 @@ func (c *cli) initTerraform() {
fatalWithDetailf(err, "reloading the configuration")
}

c.prj.root = *root
c.prj.root = root

report, vendorReport := c.gencodeWithVendor()
if report.HasFailures() {
Expand Down Expand Up @@ -2399,7 +2405,7 @@ func (c *cli) gitSafeguardRemoteEnabled() bool {

func (c *cli) wd() string { return c.prj.wd }
func (c *cli) rootdir() string { return c.prj.rootdir }
func (c *cli) cfg() *config.Root { return &c.prj.root }
func (c *cli) cfg() *config.Root { return c.prj.root }
func (c *cli) baseRef() string { return c.prj.baseRef }
func (c *cli) stackManager() *stack.Manager { return c.prj.stackManager }
func (c *cli) rootNode() hcl.Config { return c.prj.root.Tree().Node }
Expand Down Expand Up @@ -2593,8 +2599,8 @@ func newGit(basedir string) (*git.Git, error) {
return g, nil
}

func lookupProject(wd string) (prj project, found bool, err error) {
prj = project{
func lookupProject(wd string) (prj *project, found bool, err error) {
prj = &project{
wd: wd,
}

Expand All @@ -2611,37 +2617,37 @@ func lookupProject(wd string) (prj project, found bool, err error) {

rootdir, err := filepath.EvalSymlinks(gitabs)
if err != nil {
return project{}, false, errors.E(err, "failed evaluating symlinks of %q", gitabs)
return nil, false, errors.E(err, "failed evaluating symlinks of %q", gitabs)
}

cfg, err := config.LoadRoot(rootdir)
if err != nil {
return project{}, false, err
return nil, false, err
}

gw = gw.With().WorkingDir(rootdir).Wrapper()

prj.isRepo = true
prj.root = *cfg
prj.root = cfg
prj.rootdir = rootdir
prj.git.wrapper = gw

mgr := stack.NewGitAwareManager(&prj.root, gw)
mgr := stack.NewGitAwareManager(prj.root, gw)
prj.stackManager = mgr

return prj, true, nil
}

rootcfg, rootcfgpath, rootfound, err := config.TryLoadConfig(wd)
if err != nil {
return project{}, false, err
return nil, false, err
}
if !rootfound {
return project{}, false, nil
return nil, false, nil
}
prj.rootdir = rootcfgpath
prj.root = *rootcfg
prj.stackManager = stack.NewManager(&prj.root)
prj.root = rootcfg
prj.stackManager = stack.NewManager(prj.root)
return prj, true, nil
}

Expand Down
9 changes: 9 additions & 0 deletions cmd/terramate/cli/cli_noprofiling.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Copyright 2024 Terramate GmbH
// SPDX-License-Identifier: MPL-2.0

//go:build !profiler

package cli

func startProfiler(_ *cliSpec) {}
func stopProfiler(_ *cliSpec) {}
42 changes: 42 additions & 0 deletions cmd/terramate/cli/cli_profiling.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
// Copyright 2024 Terramate GmbH
// SPDX-License-Identifier: MPL-2.0

//go:build profiler

package cli

import (
stdfmt "fmt"
"os"
"runtime/pprof"
)

func startProfiler(args *cliSpec) {
if !args.CPUProfiling {
return
}
const defaultProfilerName = "terramate.prof"

fname := os.Getenv("TM_TEST_PROFILING_PATH")
if fname == "" {
fname = defaultProfilerName
}

stdfmt.Printf("Creating CPU profile (%s)...\n", fname)

f, err := os.Create(fname)
if err != nil {
fatal(err)
}
err = pprof.StartCPUProfile(f)
if err != nil {
fatal(err)
}
}

func stopProfiler(args *cliSpec) {
if !args.CPUProfiling {
return
}
pprof.StopCPUProfile()
}
2 changes: 1 addition & 1 deletion cmd/terramate/cli/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ type project struct {
rootdir string
wd string
isRepo bool
root config.Root
root *config.Root
baseRef string
repository *git.Repository
stackManager *stack.Manager
Expand Down
13 changes: 10 additions & 3 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"path/filepath"
"sort"
"strings"
"sync"

"golang.org/x/exp/slices"

Expand Down Expand Up @@ -47,7 +48,8 @@ const (
// This type is just for ensure better type checking for the cases where a
// configuration for the root directory is expected and not from anywhere else.
type Root struct {
tree Tree
// tree MUST never be nil
tree *Tree

// hasTerragruntStacks tells if the repository has any Terragrunt stack.
hasTerragruntStacks *bool
Expand Down Expand Up @@ -78,6 +80,9 @@ type Tree struct {
stack *Stack

dir string

// used for caching the loaded stack.
mu sync.Mutex
}

// DirElem represents a node which is represented by a directory.
Expand Down Expand Up @@ -128,7 +133,7 @@ func TryLoadConfig(fromdir string) (tree *Root, configpath string, found bool, e
func NewRoot(tree *Tree) *Root {
r := &Root{}
tree.root = r
r.tree = *tree
r.tree = tree

r.initRuntime()
return r
Expand All @@ -150,7 +155,7 @@ func LoadRoot(rootdir string) (*Root, error) {
}

// Tree returns the root configuration tree.
func (root *Root) Tree() *Tree { return &root.tree }
func (root *Root) Tree() *Tree { return root.tree }

// HostDir returns the root directory.
func (root *Root) HostDir() string { return root.tree.RootDir() }
Expand Down Expand Up @@ -365,6 +370,8 @@ func (tree *Tree) IsInsideStack() bool {

// Stack returns the stack object.
func (tree *Tree) Stack() (*Stack, error) {
tree.mu.Lock()
defer tree.mu.Unlock()
if tree.stack == nil {
s, err := LoadStack(tree.Root(), tree.Dir())
if err != nil {
Expand Down
Loading
Loading