| Index: perf/server/src/server/perf.go
|
| diff --git a/perf/server/src/server/perf.go b/perf/server/src/server/perf.go
|
| index 26bd16d3df56456c57efbb4451f826d81c953b47..339cfd0e91ae818c3390f6556fca005a138c735c 100644
|
| --- a/perf/server/src/server/perf.go
|
| +++ b/perf/server/src/server/perf.go
|
| @@ -18,6 +18,7 @@ import (
|
| "regexp"
|
| "sort"
|
| "strconv"
|
| + "strings"
|
| "time"
|
| )
|
|
|
| @@ -30,6 +31,8 @@ import (
|
| import (
|
| "config"
|
| "db"
|
| + "filetilestore"
|
| + "types"
|
| )
|
|
|
| var (
|
| @@ -47,6 +50,9 @@ var (
|
| 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 (
|
|
|
| var (
|
| data *Data
|
| +
|
| + tileStores map[string]types.TileStore
|
| )
|
|
|
| const (
|
| @@ -87,6 +95,11 @@ func Init() {
|
| 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 @@ func annotationsHandler(w http.ResponseWriter, r *http.Request) {
|
| }
|
| }
|
|
|
| +// 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 @@ func main() {
|
| 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))
|
|
|
|
|