blob: 073ba5cf489ed28eced6fa06c7f3dd58c5162552 [file] [log] [blame]
// 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
}