// clusteringHandler handles doing the actual k-means clustering. // // The return format is JSON of the form: // // { // "Clusters": [ // { // "Keys": [ // "x86:GeForce320M:MacMini4.1:Mac10.8:GM_varied_text_clipped_no_lcd_640_480:8888",...], // "ParamSummaries": [ // [{"Value": "Win8", "Weight": 15}, {"Value": "Android", "Weight": 14}, ...] // ], // "StepFit": { // "LeastSquares":0.0006582442047814354, // "TurningPoint":162, // "StepSize":0.023272272692293046, // "Regression": 35.3 // } // Traces: [[[0, -0.00007967326606768456], [1, 0.011877665949459049], [2, 0.012158129176717419],...]] // }, // ... // ], // "K": 5, // "StdDevThreshhold": 0.1 // } // // Note that Keys contains all the keys, while Traces only contains traces of // the N closest cluster members and the centroid. // // Takes the following query parameters: // // _k - The K to use for k-means clustering. // _stddev - The standard deviation to use when normalize traces // during k-means clustering. // _issue - The Rietveld issue ID with trybot results to include. // // Additionally the rest of the query parameters as returned from // sk.Query.selectionsAsQuery(). func clusteringHandler(w http.ResponseWriter, r *http.Request) { glog.Infof("Clustering Handler: %q\n", r.URL.Path) tile := masterTileBuilder.GetTile() w.Header().Set("Content-Type", "application/json") // If there are no query parameters just return with an empty set of ClusterSummaries. if r.FormValue("_k") == "" || r.FormValue("_stddev") == "" { writeClusterSummaries(clustering.NewClusterSummaries(), w, r) return } k, err := strconv.ParseInt(r.FormValue("_k"), 10, 32) if err != nil { util.ReportError(w, r, err, fmt.Sprintf("_k parameter must be an integer %s.", r.FormValue("_k"))) return } stddev, err := strconv.ParseFloat(r.FormValue("_stddev"), 64) if err != nil { util.ReportError(w, r, err, fmt.Sprintf("_stddev parameter must be a float %s.", r.FormValue("_stddev"))) return } issue := r.FormValue("_issue") var tryResults *types.TryBotResults = nil if issue != "" { var err error tryResults, err = trybot.Get(issue) if err != nil { util.ReportError(w, r, err, fmt.Sprintf("Failed to get trybot data for clustering.")) return } } delete(r.Form, "_k") delete(r.Form, "_stddev") delete(r.Form, "_issue") // Create a filter function for traces that match the query parameters and // optionally tryResults. filter := func(key string, tr *types.PerfTrace) bool { if tryResults != nil { if _, ok := tryResults.Values[key]; !ok { return false } } return tiling.Matches(tr, r.Form) } if issue != "" { if tile, err = trybot.TileWithTryData(tile, issue); err != nil { util.ReportError(w, r, err, fmt.Sprintf("Failed to get trybot data for clustering.")) return } } summary, err := clustering.CalculateClusterSummaries(tile, int(k), stddev, filter) if err != nil { util.ReportError(w, r, err, "Failed to calculate clusters.") return } writeClusterSummaries(summary, w, r) }
// queryHandler handles queries for and about traces. // // Queries look like: // // /query/0/-1/?arch=Arm7&arch=x86&scale=1 // // Where they keys and values in the query params are from the ParamSet. // Repeated parameters are matched via OR. I.e. the above query will include // anything that has an arch of Arm7 or x86. // // The first two path paramters are tile scale and tile number, where -1 means // the last tile at the given scale. // // The normal response is JSON of the form: // // { // "matches": 187, // } // // If the path is: // // /query/0/-1/traces/?arch=Arm7&arch=x86&scale=1 // // Then the response is the set of traces that match that query. // // { // "traces": [ // { // // All of these keys and values should be exactly what Flot consumes. // data: [[1, 1.1], [20, 30]], // label: "key1", // _params: {"os: "Android", ...} // }, // { // data: [[1.2, 2.1], [20, 35]], // label: "key2", // _params: {"os: "Android", ...} // } // ] // } // // If the path is: // // /query/0/-1/traces/?__shortcut=11 // // Then the traces in the shortcut with that ID are returned, along with the // git hash at the step function, if the shortcut came from an alert. // // { // "traces": [ // { // // All of these keys and values should be exactly what Flot consumes. // data: [[1, 1.1], [20, 30]], // label: "key1", // _params: {"os: "Android", ...} // }, // ... // ], // "hash": "a012334...", // } // // // TODO Add ability to query across a range of tiles. func queryHandler(w http.ResponseWriter, r *http.Request) { glog.Infof("Query Handler: %q\n", r.URL.Path) match := queryHandlerPath.FindStringSubmatch(r.URL.Path) glog.Infof("%#v", match) if r.Method != "GET" || match == nil || len(match) != 4 { http.NotFound(w, r) return } if err := r.ParseForm(); err != nil { util.ReportError(w, r, err, "Failed to parse query params.") } tileScale, err := strconv.ParseInt(match[1], 10, 0) if err != nil { util.ReportError(w, r, err, "Failed parsing tile scale.") return } tileNumber, err := strconv.ParseInt(match[2], 10, 0) if err != nil { util.ReportError(w, r, err, "Failed parsing tile number.") return } glog.Infof("tile: %d %d", tileScale, tileNumber) tile := masterTileBuilder.GetTile() w.Header().Set("Content-Type", "application/json") ret := &QueryResponse{ Traces: []*tiling.TraceGUI{}, Hash: "", } if match[3] == "" { // We only want the count. total := 0 for _, tr := range tile.Traces { if tiling.Matches(tr, r.Form) { total++ } } glog.Info("Count: ", total) inc := json.NewEncoder(w) if err := inc.Encode(map[string]int{"matches": total}); err != nil { glog.Errorf("Failed to write or encode output: %s", err) return } } else { // We want the matching traces. shortcutID := r.Form.Get("__shortcut") if shortcutID != "" { sh, err := shortcut.Get(shortcutID) if err != nil { http.NotFound(w, r) return } if sh.Issue != "" { if tile, err = trybot.TileWithTryData(tile, sh.Issue); err != nil { util.ReportError(w, r, err, "Failed to populate shortcut data with trybot result.") return } } ret.Hash = sh.Hash for _, k := range sh.Keys { if tr, ok := tile.Traces[k]; ok { tg := traceGuiFromTrace(tr.(*types.PerfTrace), k, tile) if tg != nil { ret.Traces = append(ret.Traces, tg) } } else if tiling.IsFormulaID(k) { // Re-evaluate the formula and add all the results to the response. formula := tiling.FormulaFromID(k) if err := addCalculatedTraces(ret, tile, formula); err != nil { glog.Errorf("Failed evaluating formula (%q) while processing shortcut %s: %s", formula, shortcutID, err) } } else if strings.HasPrefix(k, "!") { glog.Errorf("A calculated trace is slipped through: (%s) in shortcut %s: %s", k, shortcutID, err) } } } else { for key, tr := range tile.Traces { if tiling.Matches(tr, r.Form) { tg := traceGuiFromTrace(tr.(*types.PerfTrace), key, tile) if tg != nil { ret.Traces = append(ret.Traces, tg) } } } } enc := json.NewEncoder(w) if err := enc.Encode(ret); err != nil { glog.Errorf("Failed to write or encode output: %s", err) return } } }