Skip to content

Commit

Permalink
sweet: add esbuild benchmark
Browse files Browse the repository at this point in the history
This is a fairly mature CLI application that advertises its speed. The
benchmark added here is a bit short, but we can add more benchmarks or
build our own. We just want esbuild to do something interesting.

Change-Id: Ic0ed62aa17cc61315bdd96df90d3d059a560d2e2
Reviewed-on: https://go-review.googlesource.com/c/benchmarks/+/614538
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Michael Pratt <mpratt@google.com>
  • Loading branch information
mknyszek committed Sep 24, 2024
1 parent 2d3504d commit b33f78a
Show file tree
Hide file tree
Showing 4 changed files with 291 additions and 0 deletions.
168 changes: 168 additions & 0 deletions sweet/benchmarks/esbuild/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
"flag"
"fmt"
"os"
"os/exec"
"path/filepath"

"golang.org/x/benchmarks/sweet/benchmarks/internal/cgroups"
"golang.org/x/benchmarks/sweet/benchmarks/internal/driver"
"golang.org/x/benchmarks/sweet/common/diagnostics"
)

var (
esbuildBin string
esbuildSrc string
tmpDir string
benchName string
)

func init() {
driver.SetFlags(flag.CommandLine)
flag.StringVar(&esbuildBin, "bin", "", "path to esbuild binary")
flag.StringVar(&esbuildSrc, "src", "", "path to JS/TS to pack")
flag.StringVar(&tmpDir, "tmp", "", "work directory (cleared before use)")
flag.StringVar(&benchName, "bench", "", "benchmark name")
}

func main() {
flag.Parse()
if esbuildBin == "" {
fmt.Fprintln(os.Stderr, "expected non-empty bin flag")
os.Exit(1)
}
if esbuildSrc == "" {
fmt.Fprintln(os.Stderr, "expected non-empty src flag")
os.Exit(1)
}
if tmpDir == "" {
fmt.Fprintln(os.Stderr, "expected non-empty tmp flag")
os.Exit(1)
}
if err := run(benchName, esbuildBin, esbuildSrc, tmpDir); err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
}

var benchArgsFuncs = map[string]func(src, tmp string) []string{
// Args taken from https://github.com/evanw/esbuild/blob/main/Makefile.
"ThreeJS": func(src, tmp string) []string {
return []string{"--bundle",
"--global-name=THREE",
"--sourcemap",
"--minify",
"--timing",
"--outfile=" + filepath.Join(tmp, "out-three.js"),
filepath.Join(src, "src", "entry.js"),
}
},
"RomeTS": func(src, tmp string) []string {
return []string{"--bundle",
"--platform=node",
"--sourcemap",
"--minify",
"--timing",
"--outfile=" + filepath.Join(tmp, "out-rome.js"),
filepath.Join(src, "src", "entry.ts"),
}
},
"ReactAdminJS": func(src, tmp string) []string {
return []string{
"--alias:data-generator-retail=" + filepath.Join(src, "repo/examples/data-generator/src"),
"--alias:ra-core=" + filepath.Join(src, "repo/packages/ra-core/src"),
"--alias:ra-data-fakerest=" + filepath.Join(src, "repo/packages/ra-data-fakerest/src"),
"--alias:ra-data-graphql-simple=" + filepath.Join(src, "repo/packages/ra-data-graphql-simple/src"),
"--alias:ra-data-graphql=" + filepath.Join(src, "repo/packages/ra-data-graphql/src"),
"--alias:ra-data-simple-rest=" + filepath.Join(src, "repo/packages/ra-data-simple-rest/src"),
"--alias:ra-i18n-polyglot=" + filepath.Join(src, "repo/packages/ra-i18n-polyglot/src"),
"--alias:ra-input-rich-text=" + filepath.Join(src, "repo/packages/ra-input-rich-text/src"),
"--alias:ra-language-english=" + filepath.Join(src, "repo/packages/ra-language-english/src"),
"--alias:ra-language-french=" + filepath.Join(src, "repo/packages/ra-language-french/src"),
"--alias:ra-ui-materialui=" + filepath.Join(src, "repo/packages/ra-ui-materialui/src"),
"--alias:react-admin=" + filepath.Join(src, "repo/packages/react-admin/src"),
"--bundle",
"--define:process.env.REACT_APP_DATA_PROVIDER=null",
"--format=esm",
"--loader:.png=file",
"--loader:.svg=file",
"--minify",
"--sourcemap",
"--splitting",
"--target=esnext",
"--timing",
"--outdir=" + filepath.Join(tmp, "out-readmin"),
filepath.Join(src, "repo/examples/demo/src/index.tsx"),
}
},
}

func run(name, bin, src, tmp string) error {
// Get the args for this benchmark.
argsFunc, ok := benchArgsFuncs[name]
if !ok {
return fmt.Errorf("unknown benchmark %s", name)
}
cmdArgs := append([]string{bin}, argsFunc(src, tmp)...)

// Add prefix to benchmark name.
name = "ESBuild" + name

// Set up diagnostics.
var diagFiles []*driver.DiagnosticFile
diag := driver.NewDiagnostics(name)
if df, err := diag.Create(diagnostics.Perf); err != nil {
fmt.Fprintf(os.Stderr, "failed to create %s diagnostics: %s\n", diagnostics.Perf, err)
} else if df != nil {
df.Close()
diagFiles = append(diagFiles, df)

perfArgs := []string{"perf", "record", "-o", df.Name()}
perfArgs = append(perfArgs, driver.PerfFlags()...)
perfArgs = append(perfArgs, cmdArgs...)
cmdArgs = perfArgs
}
for _, typ := range []diagnostics.Type{diagnostics.CPUProfile, diagnostics.MemProfile, diagnostics.Trace} {
df, err := diag.Create(typ)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to create %s diagnostics: %s\n", typ, err)
continue
} else if df != nil {
df.Close()
diagFiles = append(diagFiles, df)

flag := "--" + string(typ)
if typ == diagnostics.MemProfile {
flag = "--heap"
}
// N.B. Flags in esbuild are fairly idiosyncratic. Flags that accept a parameter
// need to appear after an "=" character without spaces between the flag or the
// parameter.
cmdArgs = append(cmdArgs, flag+"="+df.Name())
}
}

baseCmd := exec.Command(cmdArgs[0], cmdArgs[1:]...)
baseCmd.Stdout = os.Stderr // Redirect all tool output to stderr.
baseCmd.Stderr = os.Stderr
cmd, err := cgroups.WrapCommand(baseCmd, "test.scope")
if err != nil {
return err
}
return driver.RunBenchmark(name, func(d *driver.B) error {
defer diag.Commit(d)
defer func() {
for _, df := range diagFiles {
df.Commit()
}
}()
defer d.StopTimer()
return cmd.Run()
}, []driver.RunOption{driver.DoTime(true), driver.DoAvgRSS(cmd.RSSFunc())}...)
}
7 changes: 7 additions & 0 deletions sweet/cmd/sweet/benchmark.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,12 @@ var allBenchmarks = []benchmark{
harness: harnesses.Etcd{},
generator: generators.None{},
},
{
name: "esbuild",
description: "JavaScript/Typescript bundler",
harness: &harnesses.ESBuild{},
generator: generators.None{},
},
{
name: "go-build",
description: "Go build command",
Expand Down Expand Up @@ -101,6 +107,7 @@ var benchmarkGroups = func() map[string][]*benchmark {
allBenchmarksMap["bleve-index"],
allBenchmarksMap["cockroachdb"],
allBenchmarksMap["etcd"],
allBenchmarksMap["esbuild"],
allBenchmarksMap["go-build"],
allBenchmarksMap["gopher-lua"],
}
Expand Down
1 change: 1 addition & 0 deletions sweet/cmd/sweet/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@ func TestSweetEndToEnd(t *testing.T) {
{"go-build", 4},
{"cockroachdb", 1},
{"etcd", 1},
{"esbuild", 1},
{"bleve-index", 1},
{"gopher-lua", 1},
{"markdown", 1},
Expand Down
115 changes: 115 additions & 0 deletions sweet/harnesses/esbuild.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// Copyright 2024 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package harnesses

import (
"errors"
"fmt"
"os/exec"
"path/filepath"

"golang.org/x/benchmarks/sweet/common"
"golang.org/x/benchmarks/sweet/common/log"
)

type ESBuild struct {
haveYarn bool
}

func (h *ESBuild) CheckPrerequisites() error {
// Check if we have the yarn command.
if _, err := exec.LookPath("yarn"); err == nil {
h.haveYarn = true
} else if !errors.Is(err, exec.ErrNotFound) {
return err
}
return nil
}

func (h *ESBuild) Get(gcfg *common.GetConfig) error {
err := gitShallowClone(
gcfg.SrcDir,
"https://github.com/evanw/esbuild",
"v0.23.1",
)
if err != nil {
return err
}
runMake := func(rules ...string) error {
cmd := exec.Command("make", append([]string{"-C", gcfg.SrcDir}, rules...)...)
log.TraceCommand(cmd, false)
if out, err := cmd.CombinedOutput(); err != nil {
return fmt.Errorf("failed to run make: %v: output:\n%s", err, out)
}
return nil
}
// Fetch downstream benchmark dependencies.
if err := runMake("bench/three"); err != nil {
return err
}
if h.haveYarn {
if err := runMake("bench/readmin"); err != nil {
return err
}
}
// Run the command but ignore errors. There's something weird going on with
// a sed command in this make rule, but we don't actually need it to succeed
// to run the benchmarks.
_ = runMake("bench/rome")
return nil
}

func (h *ESBuild) Build(cfg *common.Config, bcfg *common.BuildConfig) error {
// Generate a symlink to the repository and put it in bin.
// It's not a binary, but it's the only place we can put it
// and still access it in Run.
link := filepath.Join(bcfg.BinDir, "esbuild-src")
err := symlink(link, bcfg.SrcDir)
if err != nil {
return err
}
// Build driver.
if err := cfg.GoTool().BuildPath(bcfg.BenchDir, filepath.Join(bcfg.BinDir, "esbuild-bench")); err != nil {
return err
}
// Build esbuild.
return cfg.GoTool().BuildPath(filepath.Join(bcfg.SrcDir, "cmd", "esbuild"), filepath.Join(bcfg.BinDir, "esbuild"))
}

func (h *ESBuild) Run(cfg *common.Config, rcfg *common.RunConfig) error {
for _, b := range esbuildBenchmarks {
if b.needYarn && !h.haveYarn {
continue
}
cmd := exec.Command(
filepath.Join(rcfg.BinDir, "esbuild-bench"),
"-bin", filepath.Join(rcfg.BinDir, "esbuild"),
"-src", filepath.Join(rcfg.BinDir, "esbuild-src", b.src),
"-tmp", rcfg.TmpDir,
"-bench", b.name,
)
cmd.Args = append(cmd.Args, rcfg.Args...)
cmd.Env = cfg.ExecEnv.Collapse()
cmd.Stdout = rcfg.Results
cmd.Stderr = rcfg.Log
log.TraceCommand(cmd, false)
if err := cmd.Run(); err != nil {
return err
}
}
return nil
}

type esbuildBenchmark struct {
name string
src string
needYarn bool
}

var esbuildBenchmarks = []esbuildBenchmark{
{"ThreeJS", filepath.Join("bench", "three"), false},
{"RomeTS", filepath.Join("bench", "rome"), false},
{"ReactAdminJS", filepath.Join("bench", "readmin"), true},
}

0 comments on commit b33f78a

Please sign in to comment.