Skip to content

Commit

Permalink
feat: Run modules in parallel.
Browse files Browse the repository at this point in the history
  • Loading branch information
jwalton committed Apr 19, 2022
1 parent f78c9a4 commit 7bd5ef4
Show file tree
Hide file tree
Showing 7 changed files with 88 additions and 15 deletions.
3 changes: 1 addition & 2 deletions cmd/prompt.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,7 @@ var promptCmd = &cobra.Command{

// Execute the prompt.
moduleResult, promptTest := modules.RenderPrompt(&context, configuration.Prompt)

performance.EndWithChildren("Prompt", moduleResult.Performance)
performance.Add("Prompt", moduleResult.Duration, moduleResult.Performance)

if perf {
performance.Print()
Expand Down
55 changes: 48 additions & 7 deletions internal/kitsch/modules/block.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package modules

import (
"fmt"
"strings"
"text/template"

Expand Down Expand Up @@ -48,20 +49,30 @@ func (mod BlockModule) Execute(context *Context) ModuleResult {
childDurations := perf.New(len(mod.Modules))
resultsByID := make(map[string]ModuleWrapperResult, len(mod.Modules))

for index := range mod.Modules {
wrapper := &mod.Modules[index]
moduleResults := executeModules(context, mod.Modules)
for index := range moduleResults {
wrapper := mod.Modules[index]
result := moduleResults[index]

moduleDescription := wrapper.String()
childDurations.Start(moduleDescription)
result := wrapper.Execute(context)
childDurations.EndWithChildren(moduleDescription, result.Performance)
childDurations.Add(moduleDescription, result.Duration, result.Performance)

if len(result.Text) != 0 {
resultsArray = append(resultsArray, result)
}
if wrapper.config.ID != "" {
resultsByID[wrapper.config.ID] = result

id := wrapper.config.ID
if id == "" {
_, typeInUse := resultsByID[wrapper.config.Type]
if !typeInUse {
// If the module has no ID, use its type.
id = wrapper.config.Type
} else {
id = fmt.Sprintf("%s(%d:%d)", wrapper.config.Type, wrapper.Line, wrapper.Column)
}
}

resultsByID[id] = result
}

defaultText := mod.joinChildren(context, resultsArray)
Expand Down Expand Up @@ -162,3 +173,33 @@ func init() {
},
)
}

// executeModules executes an array of modules in parallel. It returns an array
// of the same length as `modules`, where each value in the resulting array
// contains the result of executing the corresponding module.
func executeModules(context *Context, modules []ModuleWrapper) []ModuleWrapperResult {
type chResult struct {
index int
value ModuleWrapperResult
}

// Create a channel to receive results from each module.
ch := make(chan chResult)

// Create a goroutine for each module.
executeModule := func(index int, module ModuleWrapper) {
ch <- chResult{index, module.Execute(context)}
}
for i, module := range modules {
go executeModule(i, module)
}

// Collect the results from the channel.
results := make([]ModuleWrapperResult, len(modules))
for i := 0; i < len(modules); i++ {
result := <-ch
results[result.index] = result.value
}

return results
}
4 changes: 0 additions & 4 deletions internal/kitsch/modules/commonConfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,5 @@ func getCommonConfig(node *yaml.Node) (CommonConfig, error) {
return config, fmt.Errorf("object is missing type (%d:%d)", node.Line, node.Column)
}

if config.ID == "" {
config.ID = config.Type
}

return config, nil
}
8 changes: 7 additions & 1 deletion internal/kitsch/modules/moduleWrapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ type ModuleWrapperResult struct {
// EndStyle is similar to StartStyle, but contains the colors of the last
// character in Text.
EndStyle styling.CharacterColors
// Duration is the time it took this module to execute.
Duration time.Duration
// Performance is an array of execution times for children of this module.
Performance *perf.Performance
}
Expand Down Expand Up @@ -86,7 +88,7 @@ func (wrapper *ModuleWrapper) UnmarshalYAML(node *yaml.Node) error {

func (wrapper ModuleWrapper) String() string {
name := wrapper.config.Type
if wrapper.config.ID != wrapper.config.Type {
if wrapper.config.ID != "" {
name = name + "#" + wrapper.config.ID
}

Expand All @@ -111,6 +113,8 @@ func (wrapper ModuleWrapper) Execute(context *Context) ModuleWrapperResult {
timeout = context.DefaultTimeout
}

start := time.Now()

// Run the module in a goroutine, so we can time it out.
ch := make(chan ModuleWrapperResult, 1)
go func() {
Expand All @@ -134,6 +138,8 @@ func (wrapper ModuleWrapper) Execute(context *Context) ModuleWrapperResult {
}
}

result.Duration = time.Since(start)

return result
}

Expand Down
9 changes: 9 additions & 0 deletions internal/kitsch/styling/styleRegistry.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@ package styling
import (
"fmt"
"strings"
"sync"

"github.com/jwalton/gchalk"
"github.com/jwalton/kitsch/internal/kitsch/log"
)

// Registry is used to store and retrieve styles.
type Registry struct {
mutex sync.Mutex

// CustomColors is a map of color names and their replacements. For example,
// if CustomColors["$foregroud"] = "red", then "$foreground" could be used in
// a style string to refer to the color red. Custom colors must start with
Expand All @@ -22,6 +25,9 @@ type Registry struct {
// AddCustomColor registers a custom color with the registry.
// The color name must start with a "$".
func (registry *Registry) AddCustomColor(name string, color string) {
registry.mutex.Lock()
defer registry.mutex.Unlock()

if registry.CustomColors == nil {
registry.CustomColors = map[string]string{}
}
Expand Down Expand Up @@ -58,6 +64,9 @@ func (registry *Registry) AddCustomColors(colors map[string]string) {
// • Any modifier accepted by `gchalk.Style()` (e.g. "bold", "dim", "inverse").
//
func (registry *Registry) Get(styleString string) (*Style, error) {
registry.mutex.Lock()
defer registry.mutex.Unlock()

if style := registry.styles[styleString]; style != nil {
return style, nil
}
Expand Down
11 changes: 11 additions & 0 deletions internal/kitsch/styling/style_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,14 @@ func TestGradient(t *testing.T) {
`error compiling style "linear-gradient(bananajoe, blue)": invalid color "bananajoe" at 0`,
)
}

func TestAddCustomColors(t *testing.T) {
styles := testStyleRegistry()
styles.AddCustomColors(map[string]string{
"$red": "#ff0000",
"$blue": "#0000ff",
})

_, err := styles.Get("$blue")
assert.NoError(t, err)
}
13 changes: 12 additions & 1 deletion internal/perf/perf.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,23 @@ func (p *Performance) EndWithChildren(description string, childPerfs *Performanc
startTime = p.lastStart
}

duration := time.Since(startTime)
p.Add(description, duration, childPerfs)
}

// Add adds execution time for an item to this Performance object.
// `description` is a unique name for this item, `duration` is the time the item
// took to execute, and `childPerfs` are any child items that were executed.
func (p *Performance) Add(
description string,
duration time.Duration,
childPerfs *Performance,
) {
var children []Record
if childPerfs != nil {
children = childPerfs.Records
}

duration := time.Since(startTime)
p.Records = append(p.Records, Record{
Description: description,
Duration: duration,
Expand Down

0 comments on commit 7bd5ef4

Please sign in to comment.