Commit df48c625 authored by Mitchell Hashimoto's avatar Mitchell Hashimoto

packer: Parallelize synopsis lookup to speed up help output

Using `time` to calculate the average of 100 iterations on my machine,
`packer` went from 130ms on average to 70ms.

Previously, the load time would scale linearly about 30ms (on my
machine) on average per new command added. Now that is much much
smaller.
parent 47606610
...@@ -8,6 +8,7 @@ FEATURES: ...@@ -8,6 +8,7 @@ FEATURES:
IMPROVEMENTS: IMPROVEMENTS:
* core: packer help output now loads much faster.
* builder/virtualbox: Do not check for VirtualBox as part of template * builder/virtualbox: Do not check for VirtualBox as part of template
validation; only check at execution. validation; only check at execution.
* builder/vmware: Do not check for VMware as part of template validation; * builder/vmware: Do not check for VMware as part of template validation;
......
...@@ -8,6 +8,7 @@ import ( ...@@ -8,6 +8,7 @@ import (
"os" "os"
"sort" "sort"
"strings" "strings"
"sync"
) )
// The function type used to lookup Builder implementations. // The function type used to lookup Builder implementations.
...@@ -68,6 +69,12 @@ type EnvironmentConfig struct { ...@@ -68,6 +69,12 @@ type EnvironmentConfig struct {
Ui Ui Ui Ui
} }
type helpCommandEntry struct {
i int
key string
synopsis string
}
// DefaultEnvironmentConfig returns a default EnvironmentConfig that can // DefaultEnvironmentConfig returns a default EnvironmentConfig that can
// be used to create a new enviroment with NewEnvironment with sane defaults. // be used to create a new enviroment with NewEnvironment with sane defaults.
func DefaultEnvironmentConfig() *EnvironmentConfig { func DefaultEnvironmentConfig() *EnvironmentConfig {
...@@ -276,30 +283,70 @@ func (e *coreEnvironment) printHelp() { ...@@ -276,30 +283,70 @@ func (e *coreEnvironment) printHelp() {
// Sort the keys // Sort the keys
sort.Strings(e.commands) sort.Strings(e.commands)
e.ui.Say("usage: packer [--version] [--help] <command> [<args>]\n") // Create the communication/sync mechanisms to get the synopsis' of
e.ui.Say("Available commands are:") // the various commands. We do this in parallel since the overhead
for _, key := range e.commands { // of the subprocess underneath is very expensive and this speeds things
var synopsis string // up an incredible amount.
var wg sync.WaitGroup
ch := make(chan *helpCommandEntry)
for i, key := range e.commands {
wg.Add(1)
// Get the synopsis in a goroutine since it may take awhile
// to subprocess out.
go func(i int, key string) {
defer wg.Done()
var synopsis string
command, err := e.components.Command(key) command, err := e.components.Command(key)
if err != nil { if err != nil {
synopsis = fmt.Sprintf("Error loading command: %s", err.Error()) synopsis = fmt.Sprintf("Error loading command: %s", err.Error())
} else if command == nil { } else if command == nil {
continue return
} else { } else {
synopsis = command.Synopsis() synopsis = command.Synopsis()
} }
// Machine-readable output of the available command
e.ui.Machine("command", key, synopsis)
// Pad the key with spaces so that they're all the same width // Pad the key with spaces so that they're all the same width
key = fmt.Sprintf("%s%s", key, strings.Repeat(" ", maxKeyLen-len(key))) key = fmt.Sprintf("%s%s", key, strings.Repeat(" ", maxKeyLen-len(key)))
// Output the command and the synopsis // Output the command and the synopsis
e.ui.Say(fmt.Sprintf(" %s %s", key, synopsis)) ch <- &helpCommandEntry{
i: i,
key: key,
synopsis: synopsis,
}
}(i, key)
} }
e.ui.Say("usage: packer [--version] [--help] <command> [<args>]\n")
e.ui.Say("Available commands are:")
// Make a goroutine that just waits for all the synopsis gathering
// to complete, and then output it.
synopsisDone := make(chan struct{})
go func() {
defer close(synopsisDone)
entries := make([]string, len(e.commands))
for entry := range ch {
e.ui.Machine("command", entry.key, entry.synopsis)
message := fmt.Sprintf(" %s %s", entry.key, entry.synopsis)
entries[entry.i] = message
}
for _, message := range entries {
if message != "" {
e.ui.Say(message)
}
}
}()
// Wait to complete getting the synopsis' then close the channel
wg.Wait()
close(ch)
<-synopsisDone
e.ui.Say("\nGlobally recognized options:") e.ui.Say("\nGlobally recognized options:")
e.ui.Say(" -machine-readable Machine-readable output format.") e.ui.Say(" -machine-readable Machine-readable output format.")
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment