all: improve local server workflow
- document local servers in README
- rename local servers to "localperf" and "localperfdata" so one can
"go get" them.
- find static/templates dirs automatically
- allow configuring a persistent database in localperfdata
Change-Id: I4e62f23c38be6978f091ccbbda8002d9f588b8a4
Reviewed-on: https://go-review.googlesource.com/37717
Reviewed-by: Russ Cox <rsc@golang.org>
diff --git a/README b/README
deleted file mode 100644
index 4ed5c68..0000000
--- a/README
+++ /dev/null
@@ -1,4 +0,0 @@
-This subrepository holds the source for various packages and tools
-related to performance measurement, storage, and analysis.
-
-To submit changes to this repository, see http://golang.org/doc/contribute.html.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..d923b4e
--- /dev/null
+++ b/README.md
@@ -0,0 +1,37 @@
+# Go performance measurement, storage, and analysis tools
+
+This subrepository holds the source for various packages and tools
+related to performance measurement, storage, and analysis.
+
+[cmd/benchstat](cmd/benchstat) contains a command-line tool that
+computes and compares statistics about benchmarks.
+
+[cmd/benchsave](cmd/benchsave) contains a command-line tool for
+publishing benchmark results.
+
+[storage](storage) contains the https://perfdata.golang.org/ benchmark
+result storage system.
+
+[analysis](analysis) contains the https://perf.golang.org/ benchmark
+result analysis system.
+
+Both storage and analysis can be run locally; the following commands will run
+the complete stack on your machine with an in-memory datastore.
+
+```
+go get -u golang.org/x/perf/storage/localperfdata
+go get -u golang.org/x/perf/analysis/localperf
+localperfdata -addr=:8081 -view_url_base=http://localhost:8080/search?q=upload: &
+localperf -addr=:8080 -storage=localhost:8081
+```
+
+The storage system is designed to have a
+[standardized API](storage/appengine/static/index.html), and we
+encourage additional analysis tools to be written against the API. A
+client can be found in [storage/client](storage/client).
+
+--
+
+Contributions to Go are appreciated. See http://golang.org/doc/contribute.html.
+
+* Bugs can be filed at the [Go issue tracker](https://golang.org/issue/new?title=x/perf:+).
diff --git a/analysis/app/app.go b/analysis/app/app.go
index d4bd819..120d3eb 100644
--- a/analysis/app/app.go
+++ b/analysis/app/app.go
@@ -16,6 +16,10 @@
type App struct {
// StorageClient is used to talk to the storage server.
StorageClient *storage.Client
+
+ // BaseDir is the directory containing the "template" directory.
+ // If empty, the current directory will be used.
+ BaseDir string
}
// RegisterOnMux registers the app's URLs on mux.
diff --git a/analysis/app/compare.go b/analysis/app/compare.go
index f154b1e..3e47fca 100644
--- a/analysis/app/compare.go
+++ b/analysis/app/compare.go
@@ -11,6 +11,7 @@
"html/template"
"io/ioutil"
"net/http"
+ "path/filepath"
"sort"
"strconv"
"strings"
@@ -140,7 +141,7 @@
q := r.Form.Get("q")
- tmpl, err := ioutil.ReadFile("template/compare.html")
+ tmpl, err := ioutil.ReadFile(filepath.Join(a.BaseDir, "template/compare.html"))
if err != nil {
http.Error(w, err.Error(), 500)
return
diff --git a/analysis/app/index.go b/analysis/app/index.go
index 99b5af3..edd4ef4 100644
--- a/analysis/app/index.go
+++ b/analysis/app/index.go
@@ -8,6 +8,7 @@
"html/template"
"io/ioutil"
"net/http"
+ "path/filepath"
"golang.org/x/perf/storage"
)
@@ -16,7 +17,7 @@
func (a *App) index(w http.ResponseWriter, r *http.Request) {
ctx := requestContext(r)
- tmpl, err := ioutil.ReadFile("template/index.html")
+ tmpl, err := ioutil.ReadFile(filepath.Join(a.BaseDir, "template/index.html"))
if err != nil {
http.Error(w, err.Error(), 500)
return
diff --git a/analysis/app/trend.go b/analysis/app/trend.go
index 9b47f6f..af03dfe 100644
--- a/analysis/app/trend.go
+++ b/analysis/app/trend.go
@@ -14,6 +14,7 @@
"io/ioutil"
"math"
"net/http"
+ "path/filepath"
"sort"
"strconv"
"strings"
@@ -38,7 +39,7 @@
q := r.Form.Get("q")
- tmpl, err := ioutil.ReadFile("template/trend.html")
+ tmpl, err := ioutil.ReadFile(filepath.Join(a.BaseDir, "template/trend.html"))
if err != nil {
http.Error(w, err.Error(), 500)
return
diff --git a/analysis/localperf/app.go b/analysis/localperf/app.go
new file mode 100644
index 0000000..a82b0c9
--- /dev/null
+++ b/analysis/localperf/app.go
@@ -0,0 +1,60 @@
+// Copyright 2017 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.
+
+// Localperf runs an HTTP server for benchmark analysis.
+//
+// Usage:
+//
+// localperf [-addr address] [-storage url] [-base_dir ../appengine]
+package main
+
+import (
+ "flag"
+ "fmt"
+ "log"
+ "net/http"
+ "os"
+
+ "golang.org/x/perf/analysis/app"
+ "golang.org/x/perf/internal/basedir"
+ "golang.org/x/perf/storage"
+)
+
+var (
+ addr = flag.String("addr", "localhost:8080", "serve HTTP on `address`")
+ storageURL = flag.String("storage", "https://perfdata.golang.org", "storage server base `url`")
+ baseDir = flag.String("base_dir", basedir.Find("golang.org/x/perf/analysis/appengine"), "base `directory` for templates")
+)
+
+func usage() {
+ fmt.Fprintf(os.Stderr, `Usage of localperf:
+ localperf [flags]
+`)
+ flag.PrintDefaults()
+ os.Exit(2)
+}
+
+func main() {
+ log.SetPrefix("localperf: ")
+ flag.Usage = usage
+ flag.Parse()
+ if flag.NArg() != 0 {
+ flag.Usage()
+ }
+
+ if *baseDir == "" {
+ log.Print("base_dir is required and could not be automatically found")
+ flag.Usage()
+ }
+
+ app := &app.App{
+ StorageClient: &storage.Client{BaseURL: *storageURL},
+ BaseDir: *baseDir,
+ }
+ app.RegisterOnMux(http.DefaultServeMux)
+
+ log.Printf("Listening on %s", *addr)
+
+ log.Fatal(http.ListenAndServe(*addr, nil))
+}
diff --git a/analysis/localserver/app.go b/analysis/localserver/app.go
deleted file mode 100644
index 6076aab..0000000
--- a/analysis/localserver/app.go
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright 2017 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.
-
-// Localserver runs an HTTP server for benchmark analysis.
-//
-// Usage:
-//
-// localserver [-addr address] [-storage url]
-package main
-
-import (
- "flag"
- "fmt"
- "log"
- "net/http"
- "os"
-
- "golang.org/x/perf/analysis/app"
- "golang.org/x/perf/storage"
-)
-
-var (
- addr = flag.String("addr", "localhost:8080", "serve HTTP on `address`")
- storageURL = flag.String("storage", "https://perfdata.golang.org", "storage server base `url`")
-)
-
-func usage() {
- fmt.Fprintf(os.Stderr, `Usage of localserver:
- localserver [flags]
-`)
- flag.PrintDefaults()
- os.Exit(2)
-}
-
-func main() {
- log.SetPrefix("localserver: ")
- flag.Usage = usage
- flag.Parse()
- if flag.NArg() != 0 {
- flag.Usage()
- }
-
- app := &app.App{StorageClient: &storage.Client{BaseURL: *storageURL}}
- app.RegisterOnMux(http.DefaultServeMux)
-
- log.Printf("Listening on %s", *addr)
-
- log.Fatal(http.ListenAndServe(*addr, nil))
-}
diff --git a/internal/basedir/basedir.go b/internal/basedir/basedir.go
new file mode 100644
index 0000000..90985c0
--- /dev/null
+++ b/internal/basedir/basedir.go
@@ -0,0 +1,58 @@
+// Copyright 2017 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 basedir finds templates and static files associated with a binary.
+package basedir
+
+import (
+ "bytes"
+ "os"
+ "os/exec"
+ "path/filepath"
+ "runtime"
+ "strings"
+)
+
+// Find locates a directory for the given package.
+// pkg should be the directory that contains the templates and/or static directories.
+// If pkg cannot be found, an empty string will be returned.
+func Find(pkg string) string {
+ cmd := exec.Command("go", "list", "-e", "-f", "{{.Dir}}", pkg)
+ if out, err := cmd.Output(); err == nil && len(out) > 0 {
+ return string(bytes.TrimRight(out, "\r\n"))
+ }
+ gopath := os.Getenv("GOPATH")
+ if gopath == "" {
+ gopath = defaultGOPATH()
+ }
+ if gopath != "" {
+ for _, dir := range strings.Split(gopath, ":") {
+ p := filepath.Join(dir, pkg)
+ if _, err := os.Stat(p); err == nil {
+ return p
+ }
+ }
+ }
+ return ""
+}
+
+// Copied from go/build/build.go
+func defaultGOPATH() string {
+ env := "HOME"
+ if runtime.GOOS == "windows" {
+ env = "USERPROFILE"
+ } else if runtime.GOOS == "plan9" {
+ env = "home"
+ }
+ if home := os.Getenv(env); home != "" {
+ def := filepath.Join(home, "go")
+ if filepath.Clean(def) == filepath.Clean(runtime.GOROOT()) {
+ // Don't set the default GOPATH to GOROOT,
+ // as that will trigger warnings from the go tool.
+ return ""
+ }
+ return def
+ }
+ return ""
+}
diff --git a/storage/app/app.go b/storage/app/app.go
index 558ff47..48e0a85 100644
--- a/storage/app/app.go
+++ b/storage/app/app.go
@@ -9,6 +9,7 @@
import (
"errors"
"net/http"
+ "path/filepath"
"golang.org/x/perf/storage/db"
"golang.org/x/perf/storage/fs"
@@ -30,6 +31,10 @@
// "viewurl" in the response from /upload. If it is non-empty,
// the upload ID will be appended to ViewURLBase.
ViewURLBase string
+
+ // BaseDir is the directory containing the "template" directory.
+ // If empty, the current directory will be used.
+ BaseDir string
}
// ErrResponseWritten can be returned by App.Auth to abort the normal /upload handling.
@@ -46,5 +51,5 @@
// index serves the readme on /
func (a *App) index(w http.ResponseWriter, r *http.Request) {
- http.ServeFile(w, r, "static/index.html")
+ http.ServeFile(w, r, filepath.Join(a.BaseDir, "static/index.html"))
}
diff --git a/storage/app/upload.go b/storage/app/upload.go
index aa983b3..eb9304e 100644
--- a/storage/app/upload.go
+++ b/storage/app/upload.go
@@ -12,6 +12,7 @@
"mime/multipart"
"net/http"
"net/url"
+ "path/filepath"
"sort"
"strings"
"time"
@@ -36,7 +37,7 @@
}
if r.Method == http.MethodGet {
- http.ServeFile(w, r, "static/upload.html")
+ http.ServeFile(w, r, filepath.Join(a.BaseDir, "static/upload.html"))
return
}
if r.Method != http.MethodPost {
diff --git a/storage/localserver/app.go b/storage/localperfdata/app.go
similarity index 61%
rename from storage/localserver/app.go
rename to storage/localperfdata/app.go
index 3a76e38..9a8d560 100644
--- a/storage/localserver/app.go
+++ b/storage/localperfdata/app.go
@@ -2,6 +2,11 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+// Localperfdata runs an HTTP server for benchmark storage.
+//
+// Usage:
+//
+// localperfdata [-addr address] [-view_url_base url] [-base_dir ../appengine] [-dsn file.db]
package main
import (
@@ -9,6 +14,7 @@
"log"
"net/http"
+ "golang.org/x/perf/internal/basedir"
"golang.org/x/perf/storage/app"
"golang.org/x/perf/storage/db"
_ "golang.org/x/perf/storage/db/sqlite3"
@@ -18,12 +24,19 @@
var (
addr = flag.String("addr", ":8080", "serve HTTP on `address`")
viewURLBase = flag.String("view_url_base", "", "/upload response with `URL` for viewing")
+ dsn = flag.String("dsn", ":memory:", "sqlite `dsn`")
+ baseDir = flag.String("base_dir", basedir.Find("golang.org/x/perf/storage/appengine"), "base `directory` for static files")
)
func main() {
flag.Parse()
- db, err := db.OpenSQL("sqlite3", ":memory:")
+ if *baseDir == "" {
+ log.Print("base_dir is required and could not be automatically found")
+ flag.Usage()
+ }
+
+ db, err := db.OpenSQL("sqlite3", *dsn)
if err != nil {
log.Fatalf("open database: %v", err)
}
@@ -34,6 +47,7 @@
FS: fs,
ViewURLBase: *viewURLBase,
Auth: func(http.ResponseWriter, *http.Request) (string, error) { return "", nil },
+ BaseDir: *baseDir,
}
app.RegisterOnMux(http.DefaultServeMux)