| // 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 app |
| |
| import ( |
| "bytes" |
| "html/template" |
| "io/ioutil" |
| "net/http" |
| "sort" |
| |
| "golang.org/x/perf/analysis/internal/benchstat" |
| "golang.org/x/perf/storage/benchfmt" |
| ) |
| |
| // A resultGroup holds a list of results and tracks the distinct labels found in that list. |
| type resultGroup struct { |
| // Raw list of results. |
| results []*benchfmt.Result |
| // LabelValues is the count of results found with each distinct (key, value) pair found in labels. |
| LabelValues map[string]map[string]int |
| } |
| |
| // add adds res to the resultGroup. |
| func (g *resultGroup) add(res *benchfmt.Result) { |
| g.results = append(g.results, res) |
| if g.LabelValues == nil { |
| g.LabelValues = make(map[string]map[string]int) |
| } |
| for k, v := range res.Labels { |
| if g.LabelValues[k] == nil { |
| g.LabelValues[k] = make(map[string]int) |
| } |
| g.LabelValues[k][v]++ |
| } |
| } |
| |
| // splitOn returns a new set of groups sharing a common value for key. |
| func (g *resultGroup) splitOn(key string) []*resultGroup { |
| groups := make(map[string]*resultGroup) |
| var values []string |
| for _, res := range g.results { |
| value := res.Labels[key] |
| if groups[value] == nil { |
| groups[value] = &resultGroup{} |
| values = append(values, value) |
| } |
| groups[value].add(res) |
| } |
| |
| sort.Strings(values) |
| var out []*resultGroup |
| for _, value := range values { |
| out = append(out, groups[value]) |
| } |
| return out |
| } |
| |
| // compare handles queries that require comparison of the groups in the query. |
| func (a *App) compare(w http.ResponseWriter, r *http.Request) { |
| if err := r.ParseForm(); err != nil { |
| http.Error(w, err.Error(), 500) |
| return |
| } |
| |
| q := r.Form.Get("q") |
| |
| tmpl, err := ioutil.ReadFile("template/compare.html") |
| if err != nil { |
| http.Error(w, err.Error(), 500) |
| return |
| } |
| |
| t, err := template.New("main").Parse(string(tmpl)) |
| if err != nil { |
| http.Error(w, err.Error(), 500) |
| return |
| } |
| |
| data := a.compareQuery(q) |
| |
| w.Header().Set("Content-Type", "text/html; charset=utf-8") |
| if err := t.Execute(w, data); err != nil { |
| http.Error(w, err.Error(), 500) |
| return |
| } |
| } |
| |
| type compareData struct { |
| Q string |
| Error string |
| Benchstat template.HTML |
| Groups []*resultGroup |
| Labels map[string]bool |
| } |
| |
| func (a *App) compareQuery(q string) *compareData { |
| // Parse query |
| queries := parseQueryString(q) |
| |
| // Send requests |
| // TODO(quentin): Issue requests in parallel? |
| var groups []*resultGroup |
| var found int |
| for _, qPart := range queries { |
| group := &resultGroup{} |
| res := a.StorageClient.Query(qPart) |
| defer res.Close() // TODO: Should happen each time through the loop |
| for res.Next() { |
| group.add(res.Result()) |
| found++ |
| } |
| err := res.Err() |
| res.Close() |
| if err != nil { |
| // TODO: If the query is invalid, surface that to the user. |
| return &compareData{ |
| Q: q, |
| Error: err.Error(), |
| } |
| } |
| groups = append(groups, group) |
| } |
| |
| if found == 0 { |
| return &compareData{ |
| Q: q, |
| Error: "No results matched the query string.", |
| }, nil |
| } |
| |
| // Attempt to automatically split results. |
| if len(groups) == 1 { |
| group := groups[0] |
| // Matching a single upload with multiple files -> split by file |
| if len(group.LabelValues["upload"]) == 1 && len(group.LabelValues["upload-part"]) > 1 { |
| groups = group.splitOn("upload-part") |
| } |
| } |
| |
| // Compute benchstat |
| var buf bytes.Buffer |
| var results [][]*benchfmt.Result |
| for _, g := range groups { |
| results = append(results, g.results) |
| } |
| benchstat.Run(&buf, results, &benchstat.Options{ |
| HTML: true, |
| }) |
| |
| // Render template. |
| labels := make(map[string]bool) |
| for _, g := range groups { |
| for k := range g.LabelValues { |
| labels[k] = true |
| } |
| } |
| data := &compareData{ |
| Q: q, |
| Benchstat: template.HTML(buf.String()), |
| Groups: groups, |
| Labels: labels, |
| } |
| return data |
| } |