Adds tile handler to the perf server
BUG=skia:
R=jcgregorio@google.com, bensong@google.com
Author: kelvinly@google.com
Review URL: https://codereview.chromium.org/382313002
diff --git a/perf/server/src/config/config.go b/perf/server/src/config/config.go
index ac05c5a..a88b80a 100644
--- a/perf/server/src/config/config.go
+++ b/perf/server/src/config/config.go
@@ -64,5 +64,33 @@
ALL_DATASET_NAMES = []DatasetName{DATASET_SKP, DATASET_MICRO}
// TODO(jcgregorio) Make into a flag.
- BEGINNING_OF_TIME = QuerySince(time.Date(2014, time.June, 18, 0, 0, 0, 0, time.UTC))
+ BEGINNING_OF_TIME = QuerySince(time.Date(2014, time.June, 18, 0, 0, 0, 0, time.UTC))
+ HUMAN_READABLE_PARAM_NAMES = map[string]string{
+ "antialias": "Antialiasing",
+ "arch": "CPU Architecture",
+ "bbh": "BBH Setting",
+ "benchName": "SKP Name",
+ "builderName": "Builder Name",
+ "config": "Picture Configuration",
+ "configuration": "Build Configuration",
+ "clip": "Clip",
+ "dither": "Dither",
+ "gpu": "GPU Type",
+ "gpuConfig": "GPU Configuration",
+ "measurementType": "Measurement Type",
+ "mode": "Mode Configuration",
+ "model": "Buildbot Model",
+ "os": "OS",
+ "role": "Buildbot Role",
+ "rotate": "Rotate",
+ "scale": "Scale Setting",
+ "skpSize": "SKP Size",
+ "system": "System Type",
+ "testName": "Test Name",
+ "viewport": "Viewport Size",
+ }
+ KEY_PARAM_ORDER = map[string][]string{
+ string(DATASET_SKP): []string{"builderName", "benchName", "config", "scale", "measurementType"},
+ string(DATASET_MICRO): []string{"builderName", "testName", "config", "scale", "measurementType"},
+ }
)
diff --git a/perf/server/src/filetilestore/filestore.go b/perf/server/src/filetilestore/filestore.go
index 9ab4c16..bbfebd3 100644
--- a/perf/server/src/filetilestore/filestore.go
+++ b/perf/server/src/filetilestore/filestore.go
@@ -91,13 +91,13 @@
t := types.NewTile()
dec := gob.NewDecoder(f)
if err := dec.Decode(t); err != nil {
- return nil, fmt.Errorf("Failed to dencode tile %s: %s", filename, err)
+ return nil, fmt.Errorf("Failed to decode tile %s: %s", filename, err)
}
return t, nil
}
-// NewFileTileStore creates a new TileStore that is back by the file system,
+// NewFileTileStore creates a new TileStore that is backed by the file system,
// where dir is the directory name and datasetName is the name of the dataset.
func NewFileTileStore(dir, datasetName string) types.TileStore {
return FileTileStore{
diff --git a/perf/server/src/server/data.go b/perf/server/src/server/data.go
index dc1ec5b..49fb557 100644
--- a/perf/server/src/server/data.go
+++ b/perf/server/src/server/data.go
@@ -169,7 +169,7 @@
type Dataset struct {
Traces []*Trace `json:"traces"`
ParamSet map[string]Choices `json:"param_set"`
- Commits []*types.Commit `json:"commits"`
+ Commits []*types.Commit `json:"commits"`
service *bigquery.Service
clusterSummaries *ClusterSummaries
}
@@ -548,19 +548,19 @@
}
// average calculates and returns the average value of the given []float64.
-func average(xs[]float64)float64 {
+func average(xs []float64) float64 {
total := 0.0
- for _,v := range xs {
+ for _, v := range xs {
total += v
}
return total / float64(len(xs))
}
// sse calculates and returns the sum squared error from the given base of []float64.
-func sse(xs[]float64, base float64)float64 {
+func sse(xs []float64, base float64) float64 {
total := 0.0
- for _,v := range xs {
- total += math.Pow(v - base, 2)
+ for _, v := range xs {
+ total += math.Pow(v-base, 2)
}
return total
}
@@ -578,7 +578,7 @@
if y0 == y1 {
continue
}
- d := math.Sqrt(sse(trace[:i], y0) + sse(trace[i:], y1)) / float64(len(trace))
+ d := math.Sqrt(sse(trace[:i], y0)+sse(trace[i:], y1)) / float64(len(trace))
if d < deviation {
deviation = d
stepSize = math.Abs(y0 - y1)
@@ -604,7 +604,7 @@
Traces: make([][][]float64, numSampleTraces),
ParamSummaries: getParamSummaries(cluster),
// Try fit on the centroid.
- StepFit: getStepFit(cluster[0].(*ctrace.ClusterableTrace).Values),
+ StepFit: getStepFit(cluster[0].(*ctrace.ClusterableTrace).Values),
}
for j, o := range cluster {
summary.Keys[j] = o.(*ctrace.ClusterableTrace).Key
diff --git a/perf/server/src/server/perf.go b/perf/server/src/server/perf.go
index 26bd16d..339cfd0 100644
--- a/perf/server/src/server/perf.go
+++ b/perf/server/src/server/perf.go
@@ -18,6 +18,7 @@
"regexp"
"sort"
"strconv"
+ "strings"
"time"
)
@@ -30,6 +31,8 @@
import (
"config"
"db"
+ "filetilestore"
+ "types"
)
var (
@@ -47,6 +50,9 @@
clustersHandlerPath = regexp.MustCompile(`/clusters/([a-z]*)$`)
shortcutHandlerPath = regexp.MustCompile(`/shortcuts/([0-9]*)$`)
+
+ // The three capture groups are dataset, tile scale, and tile number.
+ tileHandlerPath = regexp.MustCompile(`/tiles/([a-z]*)/([0-9]*)/([-0-9]*)$`)
)
// flags
@@ -59,6 +65,8 @@
var (
data *Data
+
+ tileStores map[string]types.TileStore
)
const (
@@ -87,6 +95,11 @@
indexTemplate = template.Must(template.ParseFiles(filepath.Join(cwd, "templates/index.html")))
index2Template = template.Must(template.ParseFiles(filepath.Join(cwd, "templates/index2.html")))
clusterTemplate = template.Must(template.ParseFiles(filepath.Join(cwd, "templates/clusters.html")))
+
+ tileStores = make(map[string]types.TileStore)
+ for _, name := range config.ALL_DATASET_NAMES {
+ tileStores[string(name)] = filetilestore.NewFileTileStore(*tileDir, string(name))
+ }
}
// reportError formats an HTTP error response and also logs the detailed error message.
@@ -244,6 +257,179 @@
}
}
+// makeKeyFromParams creates a trace key given the list of parameters it needs to include and the trace's parameter list.
+func makeKeyFromParams(paramList []string, params map[string]string) string {
+ newKey := make([]string, len(paramList))
+ for i, paramName := range paramList {
+ if name, ok := params[paramName]; ok {
+ newKey[i] = name
+ } else {
+ newKey[i] = ""
+ }
+ }
+ return strings.Join(newKey, ":")
+}
+
+// getTile retrieves a tile from the disk
+func getTile(dataset string, tileScale, tileNumber int) (*types.Tile, error) {
+ // TODO: Use some sort of cache
+ tileStore, ok := tileStores[dataset]
+ if !ok {
+ return nil, fmt.Errorf("Unable to access dataset store for %s", dataset)
+ }
+ tile, err := tileStore.Get(int(tileScale), int(tileNumber))
+ if err != nil || tile == nil {
+ return nil, fmt.Errorf("Unable to get tile from tilestore: ", err)
+ }
+ return tile, nil
+}
+
+// tileHandler accepts URIs like /tiles/skps/0/1?traces=Some:long:trace:here&omit_commits=true
+// where the URI format is /tiles/<dataset-name>/<tile-scale>/<tile-number>
+// It accepts a comma-delimited string of keys as traces, and
+// also omit_commits, omit_traces, and omit_names, which each cause the corresponding
+// section (described more thoroughly in types.go) to be omitted from the JSON
+func tileHandler(w http.ResponseWriter, r *http.Request) {
+ glog.Infof("Tile Handler: %q\n", r.URL.Path)
+ match := tileHandlerPath.FindStringSubmatch(r.URL.Path)
+ if r.Method != "GET" || match == nil || len(match) != 4 {
+ http.NotFound(w, r)
+ return
+ }
+ dataset := match[1]
+ tileScale, err := strconv.ParseInt(match[2], 10, 0)
+ if err != nil {
+ reportError(w, r, err, "Failed parsing tile scale.")
+ return
+ }
+ tileNumber, err := strconv.ParseInt(match[3], 10, 0)
+ if err != nil {
+ reportError(w, r, err, "Failed parsing tile number.")
+ return
+ }
+ tile, err := getTile(dataset, int(tileScale), int(tileNumber))
+ if err != nil {
+ reportError(w, r, err, "Failed to retrieve tile.")
+ return
+ }
+ tracesRequested := strings.Split(r.FormValue("traces"), ",")
+ omitCommits := r.FormValue("omit_commits") != ""
+ omitParams := r.FormValue("omit_params") != ""
+ omitNames := r.FormValue("omit_names") != ""
+ result := types.NewGUITile(int(tileScale), int(tileNumber))
+ paramList, ok := config.KEY_PARAM_ORDER[dataset]
+ if !ok {
+ reportError(w, r, err, "Unable to read parameter list for dataset: ")
+ return
+ }
+ for _, keyName := range tracesRequested {
+ if len(keyName) <= 0 {
+ continue
+ }
+ var rawTrace *types.Trace
+ count := 0
+ // Unpack trace name and find the trace.
+ keyParts := strings.Split(keyName, ":")
+ for _, tileTrace := range tile.Traces {
+ tracesMatch := true
+ for i, keyPart := range keyParts {
+ if len(keyPart) > 0 {
+ if traceParam, exists := tileTrace.Params[paramList[i]]; !exists || traceParam != keyPart {
+ tracesMatch = false
+ break
+ }
+ // If it doesn't exist in the key, it should also not exist in
+ // the trace parameters
+ } else if traceParam, exists := tileTrace.Params[paramList[i]]; exists && len(traceParam) <= 0 {
+ tracesMatch = false
+ break
+ }
+ }
+ if tracesMatch {
+ rawTrace = tileTrace
+ // NOTE: Not breaking out of the loop
+ // for now to see if there are multiple
+ // traces that match any given trace
+ count += 1
+ }
+ }
+ // No matches
+ if count <= 0 || rawTrace == nil {
+ continue
+ } else {
+ if count > 1 {
+ glog.Warningln(count, "matches found for ", keyName)
+ }
+ }
+ newTraceData := make([][2]float64, 0)
+ for i, traceVal := range rawTrace.Values {
+ if traceVal != config.MISSING_DATA_SENTINEL {
+ newTraceData = append(newTraceData, [2]float64{
+ float64(tile.Commits[i].CommitTime),
+ traceVal,
+ // We should have 53 significand bits, so this should work correctly basically forever
+ })
+ }
+ }
+ if len(newTraceData) > 0 {
+ result.Traces = append(result.Traces, types.TraceGUI{
+ Data: newTraceData,
+ Key: keyName,
+ })
+ }
+ }
+ if !omitCommits {
+ result.Commits = tile.Commits
+ }
+ if !omitNames {
+ for _, trace := range tile.Traces {
+ result.NameList = append(result.NameList, makeKeyFromParams(paramList, trace.Params))
+ }
+ }
+ if !omitParams {
+ // NOTE: When constructing ParamSet, we need to make sure there are empty strings
+ // where there's at least one key missing that parameter.
+ // TODO: Fix this in tile generation rather than here.
+ result.ParamSet = make([][]string, len(paramList))
+ for i := range result.ParamSet {
+ if readableName, ok := config.HUMAN_READABLE_PARAM_NAMES[paramList[i]]; !ok {
+ glog.Warningln(fmt.Sprintf("%s does not exist in the readable parameter names list", paramList[i]))
+ result.ParamSet[i] = []string{paramList[i]}
+ } else {
+ result.ParamSet[i] = []string{readableName}
+ }
+ }
+ for _, trace := range tile.Traces {
+ for i := range result.ParamSet {
+ traceValue, ok := trace.Params[paramList[i]]
+ if !ok {
+ traceValue = ""
+ }
+ traceValueIsInParamSet := false
+ for _, param := range []string(result.ParamSet[i]) {
+ if param == traceValue {
+ traceValueIsInParamSet = true
+ }
+ }
+ if !traceValueIsInParamSet {
+ result.ParamSet[i] = append(result.ParamSet[i], traceValue)
+ }
+ }
+ }
+ }
+ // Marshal and send
+ marshaledResult, err := json.Marshal(result)
+ if err != nil {
+ reportError(w, r, err, "Failed to marshal JSON.")
+ return
+ }
+ w.Header().Set("Content-Type", "application/json")
+ _, err = w.Write(marshaledResult)
+ if err != nil {
+ reportError(w, r, err, "Error while marshalling results.")
+ }
+}
+
// jsonHandler handles the GET for the JSON requests.
func jsonHandler(w http.ResponseWriter, r *http.Request) {
glog.Infof("JSON Handler: %q\n", r.URL.Path)
@@ -306,6 +492,7 @@
http.HandleFunc("/index2", autogzip.HandleFunc(main2Handler))
http.HandleFunc("/json/", jsonHandler) // We pre-gzip this ourselves.
http.HandleFunc("/shortcuts/", shortcutHandler)
+ http.HandleFunc("/tiles/", tileHandler)
http.HandleFunc("/clusters/", autogzip.HandleFunc(clusterHandler))
http.HandleFunc("/annotations/", autogzip.HandleFunc(annotationsHandler))
diff --git a/perf/server/src/types/types.go b/perf/server/src/types/types.go
index 5a8bc42..d476c1a 100644
--- a/perf/server/src/types/types.go
+++ b/perf/server/src/types/types.go
@@ -86,6 +86,32 @@
}
}
+// TraceGUI is used in TileGUI.
+type TraceGUI struct {
+ Data [][2]float64 `json:"data"`
+ Key string `json:"key"`
+}
+
+// TileGUI is the JSON the server serves for tile requests.
+type TileGUI struct {
+ Traces []TraceGUI `json:"traces,omitempty"`
+ ParamSet [][]string `json:"params,omitempty"`
+ Commits []*Commit `json:"commits,omitempty"`
+ NameList []string `json:"names,omitempty"`
+ Scale int `json:"scale"`
+ TileIndex int `json:"tileIndex"`
+}
+
+func NewGUITile(scale int, tileIndex int) *TileGUI {
+ return &TileGUI{
+ Traces: make([]TraceGUI, 0),
+ ParamSet: make([][]string, 0),
+ Commits: make([]*Commit, 0),
+ Scale: scale,
+ TileIndex: tileIndex,
+ }
+}
+
// TileStore is an interface representing the ability to save and restore Tiles.
type TileStore interface {
Put(scale, index int, tile *Tile) error