Skip to content

Commit

Permalink
feat: Add profile to pixlet check. (#811)
Browse files Browse the repository at this point in the history
This change adds profiling to the pixlet check command. In addition, it
adds a render check given it needs to be able to render before we can
profile an app. Finally, I've made improvements to output silencing so
the pixlet check output is clear.
  • Loading branch information
betterengineering authored Jun 7, 2023
1 parent ccf92b1 commit e00ee60
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 42 deletions.
43 changes: 31 additions & 12 deletions cmd/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"os"
"path/filepath"
"strings"
"time"

"github.com/bazelbuild/buildtools/buildifier/utils"
"github.com/fatih/color"
Expand All @@ -13,6 +14,8 @@ import (
"tidbyt.dev/pixlet/manifest"
)

const MaxRenderTime = 500000000 // 500ms

func init() {
CheckCmd.Flags().BoolVarP(&rflag, "recursive", "r", false, "find apps recursively")
}
Expand Down Expand Up @@ -60,16 +63,6 @@ func checkCmd(cmd *cobra.Command, args []string) error {
continue
}

// Create temporary file for app rendering.
f, err := os.CreateTemp("", "")
if err != nil {
return fmt.Errorf("could not create temp file for rendering, check your system: %w", err)
}
defer os.Remove(f.Name())

// TODO: Check if app will render once we are able to enable target
// determination.

// Check if an app can load.
err = community.LoadApp(cmd, []string{app})
if err != nil {
Expand Down Expand Up @@ -121,8 +114,34 @@ func checkCmd(cmd *cobra.Command, args []string) error {
failure(app, fmt.Errorf("manifest contains spelling errors: %w", err), fmt.Sprintf("try `pixlet community spell-check --fix %s`", manifestFile))
continue
}
// TODO: enable spell check for apps once we can run it successfully
// against the community repo.

// Create temporary file for app rendering.
f, err := os.CreateTemp("", "")
if err != nil {
return fmt.Errorf("could not create temp file for rendering, check your system: %w", err)
}
defer os.Remove(f.Name())

// Check if app renders.
silenceOutput = true
output = f.Name()
err = render(cmd, []string{app})
if err != nil {
foundIssue = true
failure(app, fmt.Errorf("app failed to render: %w", err), "try `pixlet render` and resolve any runtime issues")
continue
}

// Check performance.
p, err := ProfileApp(app, map[string]string{})
if err != nil {
return fmt.Errorf("could not profile app: %w", err)
}
if p.DurationNanos > MaxRenderTime {
foundIssue = true
failure(app, fmt.Errorf("app takes too long to render %s", time.Duration(p.DurationNanos)), fmt.Sprintf("try optimizing your app using `pixlet profile %s` to get it under %s", app, time.Duration(MaxRenderTime)))
continue
}

// If we're here, the app and manifest are good to go!
success(app)
Expand Down
10 changes: 9 additions & 1 deletion cmd/community/loadapp.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"strings"

"github.com/spf13/cobra"
"go.starlark.net/starlark"
"tidbyt.dev/pixlet/runtime"
)

Expand Down Expand Up @@ -34,8 +35,15 @@ func LoadApp(cmd *cobra.Command, args []string) error {
runtime.InitHTTP(cache)
runtime.InitCache(cache)

// Remove the print function from the starlark thread.
initializers := []runtime.ThreadInitializer{}
initializers = append(initializers, func(thread *starlark.Thread) *starlark.Thread {
thread.Print = func(thread *starlark.Thread, msg string) {}
return thread
})

applet := runtime.Applet{}
err = applet.Load(script, src, nil)
err = applet.LoadWithInitializers(script, src, nil, initializers...)
if err != nil {
return fmt.Errorf("failed to load applet: %w", err)
}
Expand Down
10 changes: 9 additions & 1 deletion cmd/community/validateicons.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"os"

"github.com/spf13/cobra"
"go.starlark.net/starlark"
"tidbyt.dev/pixlet/icons"
"tidbyt.dev/pixlet/runtime"
"tidbyt.dev/pixlet/schema"
Expand Down Expand Up @@ -38,8 +39,15 @@ func ValidateIcons(cmd *cobra.Command, args []string) error {
runtime.InitHTTP(cache)
runtime.InitCache(cache)

// Remove the print function from the starlark thread.
initializers := []runtime.ThreadInitializer{}
initializers = append(initializers, func(thread *starlark.Thread) *starlark.Thread {
thread.Print = func(thread *starlark.Thread, msg string) {}
return thread
})

applet := runtime.Applet{}
err = applet.Load(args[0], src, nil)
err = applet.LoadWithInitializers(args[0], src, nil, initializers...)
if err != nil {
return fmt.Errorf("failed to load applet: %w", err)
}
Expand Down
50 changes: 33 additions & 17 deletions cmd/profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,48 +80,64 @@ func profile(cmd *cobra.Command, args []string) error {
config[split[0]] = split[1]
}

profile, err := ProfileApp(script, config)
if err != nil {
return err
}

options := &pprof_driver.Options{
Fetch: MakeFetchFunc(profile),
UI: printUI{},
}
if err = pprof_driver.PProf(options); err != nil {
return fmt.Errorf("could not start pprof driver: %w", err)
}

return nil
}

func ProfileApp(script string, config map[string]string) (*pprof_profile.Profile, error) {
src, err := ioutil.ReadFile(script)
if err != nil {
return fmt.Errorf("failed to read file %s: %w", script, err)
return nil, fmt.Errorf("failed to read file %s: %w", script, err)
}

cache := runtime.NewInMemoryCache()
runtime.InitHTTP(cache)
runtime.InitCache(cache)

// Remove the print function from the starlark thread.
initializers := []runtime.ThreadInitializer{}
initializers = append(initializers, func(thread *starlark.Thread) *starlark.Thread {
thread.Print = func(thread *starlark.Thread, msg string) {}
return thread
})

applet := runtime.Applet{}
err = applet.Load(script, src, nil)
err = applet.LoadWithInitializers(script, src, nil, initializers...)
if err != nil {
return fmt.Errorf("failed to load applet: %w", err)
return nil, fmt.Errorf("failed to load applet: %w", err)
}

buf := new(bytes.Buffer)
if err = starlark.StartProfile(buf); err != nil {
return fmt.Errorf("error starting profiler: %w", err)
return nil, fmt.Errorf("error starting profiler: %w", err)
}

_, err = applet.Run(config)
_, err = applet.Run(config, initializers...)
if err != nil {
_ = starlark.StopProfile()
return fmt.Errorf("error running script: %w", err)
return nil, fmt.Errorf("error running script: %w", err)
}

if err = starlark.StopProfile(); err != nil {
return fmt.Errorf("error stopping profiler: %w", err)
return nil, fmt.Errorf("error stopping profiler: %w", err)
}

profile, err := pprof_profile.ParseData(buf.Bytes())
if err != nil {
return fmt.Errorf("could not parse pprof profile: %w", err)
}

options := &pprof_driver.Options{
Fetch: MakeFetchFunc(profile),
UI: printUI{},
}
if err = pprof_driver.PProf(options); err != nil {
return fmt.Errorf("could not start pprof driver: %w", err)
return nil, fmt.Errorf("could not parse pprof profile: %w", err)
}

return nil
return profile, nil
}
20 changes: 10 additions & 10 deletions cmd/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,16 +100,6 @@ func render(cmd *cobra.Command, args []string) error {
return fmt.Errorf("failed to read file %s: %w", script, err)
}

cache := runtime.NewInMemoryCache()
runtime.InitHTTP(cache)
runtime.InitCache(cache)

applet := runtime.Applet{}
err = applet.Load(script, src, nil)
if err != nil {
return fmt.Errorf("failed to load applet: %w", err)
}

// Remove the print function from the starlark thread if the silent flag is
// passed.
initializers := []runtime.ThreadInitializer{}
Expand All @@ -120,6 +110,16 @@ func render(cmd *cobra.Command, args []string) error {
})
}

cache := runtime.NewInMemoryCache()
runtime.InitHTTP(cache)
runtime.InitCache(cache)

applet := runtime.Applet{}
err = applet.LoadWithInitializers(script, src, nil, initializers...)
if err != nil {
return fmt.Errorf("failed to load applet: %w", err)
}

roots, err := applet.Run(config, initializers...)
if err != nil {
return fmt.Errorf("error running script: %w", err)
Expand Down
6 changes: 5 additions & 1 deletion runtime/applet.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ func (a *Applet) thread(initializers ...ThreadInitializer) *starlark.Thread {
// and the actual code should be passed in src. Optionally also pass
// loader to make additional starlark modules available to the script.
func (a *Applet) Load(filename string, src []byte, loader ModuleLoader) (err error) {
return a.LoadWithInitializers(filename, src, loader)
}

func (a *Applet) LoadWithInitializers(filename string, src []byte, loader ModuleLoader, initializers ...ThreadInitializer) (err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("panic while executing %s: %v", a.Filename, r)
Expand All @@ -116,7 +120,7 @@ func (a *Applet) Load(filename string, src []byte, loader ModuleLoader) (err err
"struct": starlark.NewBuiltin("struct", starlarkstruct.Make),
}

globals, err := starlark.ExecFile(a.thread(), a.Filename, a.src, a.predeclared)
globals, err := starlark.ExecFile(a.thread(initializers...), a.Filename, a.src, a.predeclared)
if err != nil {
return fmt.Errorf("starlark.ExecFile: %v", err)
}
Expand Down

0 comments on commit e00ee60

Please sign in to comment.