Commit 7d6bbd1e authored by Quentin Smith's avatar Quentin Smith

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/37717Reviewed-by: default avatarRuss Cox <rsc@golang.org>
parent 52d96bae
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.
# 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:+).
...@@ -16,6 +16,10 @@ import ( ...@@ -16,6 +16,10 @@ import (
type App struct { type App struct {
// StorageClient is used to talk to the storage server. // StorageClient is used to talk to the storage server.
StorageClient *storage.Client 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. // RegisterOnMux registers the app's URLs on mux.
......
...@@ -11,6 +11,7 @@ import ( ...@@ -11,6 +11,7 @@ import (
"html/template" "html/template"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"path/filepath"
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
...@@ -140,7 +141,7 @@ func (a *App) compare(w http.ResponseWriter, r *http.Request) { ...@@ -140,7 +141,7 @@ func (a *App) compare(w http.ResponseWriter, r *http.Request) {
q := r.Form.Get("q") 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 { if err != nil {
http.Error(w, err.Error(), 500) http.Error(w, err.Error(), 500)
return return
......
...@@ -8,6 +8,7 @@ import ( ...@@ -8,6 +8,7 @@ import (
"html/template" "html/template"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"path/filepath"
"golang.org/x/perf/storage" "golang.org/x/perf/storage"
) )
...@@ -16,7 +17,7 @@ import ( ...@@ -16,7 +17,7 @@ import (
func (a *App) index(w http.ResponseWriter, r *http.Request) { func (a *App) index(w http.ResponseWriter, r *http.Request) {
ctx := requestContext(r) ctx := requestContext(r)
tmpl, err := ioutil.ReadFile("template/index.html") tmpl, err := ioutil.ReadFile(filepath.Join(a.BaseDir, "template/index.html"))
if err != nil { if err != nil {
http.Error(w, err.Error(), 500) http.Error(w, err.Error(), 500)
return return
......
...@@ -14,6 +14,7 @@ import ( ...@@ -14,6 +14,7 @@ import (
"io/ioutil" "io/ioutil"
"math" "math"
"net/http" "net/http"
"path/filepath"
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
...@@ -38,7 +39,7 @@ func (a *App) trend(w http.ResponseWriter, r *http.Request) { ...@@ -38,7 +39,7 @@ func (a *App) trend(w http.ResponseWriter, r *http.Request) {
q := r.Form.Get("q") 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 { if err != nil {
http.Error(w, err.Error(), 500) http.Error(w, err.Error(), 500)
return return
......
...@@ -2,11 +2,11 @@ ...@@ -2,11 +2,11 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
// Localserver runs an HTTP server for benchmark analysis. // Localperf runs an HTTP server for benchmark analysis.
// //
// Usage: // Usage:
// //
// localserver [-addr address] [-storage url] // localperf [-addr address] [-storage url] [-base_dir ../appengine]
package main package main
import ( import (
...@@ -17,31 +17,41 @@ import ( ...@@ -17,31 +17,41 @@ import (
"os" "os"
"golang.org/x/perf/analysis/app" "golang.org/x/perf/analysis/app"
"golang.org/x/perf/internal/basedir"
"golang.org/x/perf/storage" "golang.org/x/perf/storage"
) )
var ( var (
addr = flag.String("addr", "localhost:8080", "serve HTTP on `address`") addr = flag.String("addr", "localhost:8080", "serve HTTP on `address`")
storageURL = flag.String("storage", "https://perfdata.golang.org", "storage server base `url`") 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() { func usage() {
fmt.Fprintf(os.Stderr, `Usage of localserver: fmt.Fprintf(os.Stderr, `Usage of localperf:
localserver [flags] localperf [flags]
`) `)
flag.PrintDefaults() flag.PrintDefaults()
os.Exit(2) os.Exit(2)
} }
func main() { func main() {
log.SetPrefix("localserver: ") log.SetPrefix("localperf: ")
flag.Usage = usage flag.Usage = usage
flag.Parse() flag.Parse()
if flag.NArg() != 0 { if flag.NArg() != 0 {
flag.Usage() flag.Usage()
} }
app := &app.App{StorageClient: &storage.Client{BaseURL: *storageURL}} 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) app.RegisterOnMux(http.DefaultServeMux)
log.Printf("Listening on %s", *addr) log.Printf("Listening on %s", *addr)
......
// 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 ""
}
...@@ -9,6 +9,7 @@ package app ...@@ -9,6 +9,7 @@ package app
import ( import (
"errors" "errors"
"net/http" "net/http"
"path/filepath"
"golang.org/x/perf/storage/db" "golang.org/x/perf/storage/db"
"golang.org/x/perf/storage/fs" "golang.org/x/perf/storage/fs"
...@@ -30,6 +31,10 @@ type App struct { ...@@ -30,6 +31,10 @@ type App struct {
// "viewurl" in the response from /upload. If it is non-empty, // "viewurl" in the response from /upload. If it is non-empty,
// the upload ID will be appended to ViewURLBase. // the upload ID will be appended to ViewURLBase.
ViewURLBase string 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. // ErrResponseWritten can be returned by App.Auth to abort the normal /upload handling.
...@@ -46,5 +51,5 @@ func (a *App) RegisterOnMux(mux *http.ServeMux) { ...@@ -46,5 +51,5 @@ func (a *App) RegisterOnMux(mux *http.ServeMux) {
// index serves the readme on / // index serves the readme on /
func (a *App) index(w http.ResponseWriter, r *http.Request) { 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"))
} }
...@@ -12,6 +12,7 @@ import ( ...@@ -12,6 +12,7 @@ import (
"mime/multipart" "mime/multipart"
"net/http" "net/http"
"net/url" "net/url"
"path/filepath"
"sort" "sort"
"strings" "strings"
"time" "time"
...@@ -36,7 +37,7 @@ func (a *App) upload(w http.ResponseWriter, r *http.Request) { ...@@ -36,7 +37,7 @@ func (a *App) upload(w http.ResponseWriter, r *http.Request) {
} }
if r.Method == http.MethodGet { if r.Method == http.MethodGet {
http.ServeFile(w, r, "static/upload.html") http.ServeFile(w, r, filepath.Join(a.BaseDir, "static/upload.html"))
return return
} }
if r.Method != http.MethodPost { if r.Method != http.MethodPost {
......
...@@ -2,6 +2,11 @@ ...@@ -2,6 +2,11 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // 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 package main
import ( import (
...@@ -9,6 +14,7 @@ import ( ...@@ -9,6 +14,7 @@ import (
"log" "log"
"net/http" "net/http"
"golang.org/x/perf/internal/basedir"
"golang.org/x/perf/storage/app" "golang.org/x/perf/storage/app"
"golang.org/x/perf/storage/db" "golang.org/x/perf/storage/db"
_ "golang.org/x/perf/storage/db/sqlite3" _ "golang.org/x/perf/storage/db/sqlite3"
...@@ -18,12 +24,19 @@ import ( ...@@ -18,12 +24,19 @@ import (
var ( var (
addr = flag.String("addr", ":8080", "serve HTTP on `address`") addr = flag.String("addr", ":8080", "serve HTTP on `address`")
viewURLBase = flag.String("view_url_base", "", "/upload response with `URL` for viewing") 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() { func main() {
flag.Parse() 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 { if err != nil {
log.Fatalf("open database: %v", err) log.Fatalf("open database: %v", err)
} }
...@@ -34,6 +47,7 @@ func main() { ...@@ -34,6 +47,7 @@ func main() {
FS: fs, FS: fs,
ViewURLBase: *viewURLBase, ViewURLBase: *viewURLBase,
Auth: func(http.ResponseWriter, *http.Request) (string, error) { return "", nil }, Auth: func(http.ResponseWriter, *http.Request) (string, error) { return "", nil },
BaseDir: *baseDir,
} }
app.RegisterOnMux(http.DefaultServeMux) app.RegisterOnMux(http.DefaultServeMux)
......
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