1
0
mirror of https://github.com/golang/go synced 2024-10-01 01:08:33 -06:00

dashboard: improve graph view

Several changes as per Russ and Ian requests:

1. Fix almost broken ZoomIn/ZoomOut/Newer/Older with ability zoom in/out and move left/right w/o reloading (the 'explorer' attribute on graph).

2. Start the graph from the current release by default.

3. Allow to select the range of commits by specifying release range (e.g. go1.1 to go1.3 or go1.3 to tip).

4. Make it visually clear that you can select several benchmarks/metrics (replace select with a set of checkboxes).

5. Remove the "absolute" mode. Instead normalize all metrics to the start of the release (start becomes 1.0) and all subsequent changes are relative to it.

LGTM=adg
R=adg
CC=golang-codereviews, iant, rsc
https://golang.org/cl/159980043
This commit is contained in:
Dmitriy Vyukov 2014-10-17 11:34:53 +04:00
parent 4201ff03b5
commit 3eede9be08
3 changed files with 110 additions and 119 deletions

View File

@ -207,10 +207,12 @@ func findMetric(c *perfChangesCommit, metric string) *perfChangesMetric {
} }
type uiPerfConfig struct { type uiPerfConfig struct {
Builders []uiPerfConfigElem Builders []uiPerfConfigElem
Benchmarks []uiPerfConfigElem Benchmarks []uiPerfConfigElem
Metrics []uiPerfConfigElem Metrics []uiPerfConfigElem
Procs []uiPerfConfigElem Procs []uiPerfConfigElem
CommitsFrom []uiPerfConfigElem
CommitsTo []uiPerfConfigElem
} }
type uiPerfConfigElem struct { type uiPerfConfigElem struct {

View File

@ -36,7 +36,6 @@ func perfGraphHandler(w http.ResponseWriter, r *http.Request) {
allMetrics := pc.MetricsForBenchmark("") allMetrics := pc.MetricsForBenchmark("")
allProcs := pc.ProcList("") allProcs := pc.ProcList("")
r.ParseForm() r.ParseForm()
absolute := r.FormValue("absolute") != ""
selBuilders := r.Form["builder"] selBuilders := r.Form["builder"]
selBenchmarks := r.Form["benchmark"] selBenchmarks := r.Form["benchmark"]
selMetrics := r.Form["metric"] selMetrics := r.Form["metric"]
@ -53,8 +52,55 @@ func perfGraphHandler(w http.ResponseWriter, r *http.Request) {
if len(selProcs) == 0 { if len(selProcs) == 0 {
selProcs = append(selProcs, "1") selProcs = append(selProcs, "1")
} }
commitFrom := r.FormValue("commit-from")
if commitFrom == "" {
commitFrom = lastRelease
}
commitTo := r.FormValue("commit-to")
if commitTo == "" {
commitTo = "tip"
}
// TODO(dvyukov): validate input // TODO(dvyukov): validate input
// Figure out start and end commit from commitFrom/commitTo.
startCommitNum := 0
endCommitNum := 0
{
comFrom := &Commit{Hash: knownTags[commitFrom]}
if err := datastore.Get(c, comFrom.Key(c), comFrom); err != nil {
logErr(w, r, err)
return
}
startCommitNum = comFrom.Num - 1
retry:
if commitTo == "tip" {
p, err := GetPackage(c, "")
if err != nil {
logErr(w, r, err)
return
}
endCommitNum = p.NextNum
} else {
comTo := &Commit{Hash: knownTags[commitTo]}
if err := datastore.Get(c, comTo.Key(c), comTo); err != nil {
logErr(w, r, err)
return
}
endCommitNum = comTo.Num
}
if endCommitNum <= startCommitNum {
// User probably selected from:go1.3 to:go1.2. Fix go1.2 to tip.
if commitTo == "tip" {
logErr(w, r, fmt.Errorf("no commits to display (%v-%v)", commitFrom, commitTo))
return
}
commitTo = "tip"
goto retry
}
}
commitsToDisplay := endCommitNum - startCommitNum
present := func(set []string, s string) bool { present := func(set []string, s string) bool {
for _, s1 := range set { for _, s1 := range set {
if s1 == s { if s1 == s {
@ -77,43 +123,14 @@ func perfGraphHandler(w http.ResponseWriter, r *http.Request) {
for _, v := range allProcs { for _, v := range allProcs {
cfg.Procs = append(cfg.Procs, uiPerfConfigElem{strconv.Itoa(v), present(selProcs, strconv.Itoa(v))}) cfg.Procs = append(cfg.Procs, uiPerfConfigElem{strconv.Itoa(v), present(selProcs, strconv.Itoa(v))})
} }
for k := range knownTags {
// Select last commit. cfg.CommitsFrom = append(cfg.CommitsFrom, uiPerfConfigElem{k, commitFrom == k})
startCommit := 0
commitsToDisplay := 100
if r.FormValue("startcommit") != "" {
startCommit, _ = strconv.Atoi(r.FormValue("startcommit"))
commitsToDisplay, _ = strconv.Atoi(r.FormValue("commitnum"))
} else {
var commits1 []*Commit
_, err = datastore.NewQuery("Commit").
Ancestor((&Package{}).Key(c)).
Order("-Num").
Filter("NeedsBenchmarking =", true).
Limit(1).
GetAll(c, &commits1)
if err != nil || len(commits1) != 1 {
logErr(w, r, err)
return
}
startCommit = commits1[0].Num
} }
for k := range knownTags {
if r.FormValue("zoomin") != "" { cfg.CommitsTo = append(cfg.CommitsTo, uiPerfConfigElem{k, commitTo == k})
commitsToDisplay /= 2
} else if r.FormValue("zoomout") != "" {
commitsToDisplay *= 2
} else if r.FormValue("older") != "" {
startCommit -= commitsToDisplay / 2
} else if r.FormValue("newer") != "" {
startCommit += commitsToDisplay / 2
} }
cfg.CommitsTo = append(cfg.CommitsTo, uiPerfConfigElem{"tip", commitTo == "tip"})
// TODO(dvyukov): limit number of lines on the graph?
startCommitNum := startCommit - commitsToDisplay + 1
if startCommitNum < 0 {
startCommitNum = 0
}
var vals [][]float64 var vals [][]float64
var hints [][]string var hints [][]string
var certainty [][]bool var certainty [][]bool
@ -133,39 +150,13 @@ func perfGraphHandler(w http.ResponseWriter, r *http.Request) {
logErr(w, r, err) logErr(w, r, err)
return return
} }
nonzero := false hasdata := false
min := ^uint64(0)
max := uint64(0)
for _, v := range vv { for _, v := range vv {
if v == 0 { if v != 0 {
continue hasdata = true
} }
if max < v {
max = v
}
if min > v {
min = v
}
nonzero = true
} }
if nonzero { if hasdata {
noise := pc.NoiseLevel(builder, benchProcs, metric)
diff := (float64(max) - float64(min)) / float64(max) * 100
// Scale graph passes through 2 points: (noise, minScale) and (growthFactor*noise, 100).
// Plus it's bottom capped at minScale and top capped at 100.
// Intention:
// Diffs below noise are scaled to minScale.
// Diffs above growthFactor*noise are scaled to 100.
// Between noise and growthFactor*noise scale growths linearly.
const minScale = 5
const growthFactor = 4
scale := diff*(100-minScale)/(noise*(growthFactor-1)) + (minScale*growthFactor-100)/(growthFactor-1)
if scale < minScale {
scale = minScale
}
if scale > 100 {
scale = 100
}
descBuilder := "/" + builder descBuilder := "/" + builder
descBenchmark := "/" + benchProcs descBenchmark := "/" + benchProcs
descMetric := "/" + metric descMetric := "/" + metric
@ -182,6 +173,7 @@ func perfGraphHandler(w http.ResponseWriter, r *http.Request) {
hh := make([]string, commitsToDisplay) hh := make([]string, commitsToDisplay)
valf := make([]float64, commitsToDisplay) valf := make([]float64, commitsToDisplay)
cert := make([]bool, commitsToDisplay) cert := make([]bool, commitsToDisplay)
firstval := uint64(0)
lastval := uint64(0) lastval := uint64(0)
lastval0 := uint64(0) lastval0 := uint64(0)
for i, v := range vv { for i, v := range vv {
@ -204,26 +196,20 @@ func perfGraphHandler(w http.ResponseWriter, r *http.Request) {
} }
cert[i] = false cert[i] = false
v = lastval + uint64(int64(nextval-lastval)/int64(nextidx-i+1)) v = lastval + uint64(int64(nextval-lastval)/int64(nextidx-i+1))
_, _ = nextval, nextidx
} }
f := float64(v) if firstval == 0 {
if !absolute { firstval = v
f = (float64(v) - float64(min)) * 100 / (float64(max) - float64(min))
f = f*scale/100 + (100-scale)/2
f += 0.000001
} }
valf[i] = f valf[i] = float64(v) / float64(firstval)
com := commits2[i]
comLink := "https://code.google.com/p/go/source/detail?r=" + com.Hash
if cert[i] { if cert[i] {
d := "" d := ""
if lastval0 != 0 { if lastval0 != 0 {
d = fmt.Sprintf(" (%.02f%%)", perfDiff(lastval0, v)) d = fmt.Sprintf(" (%.02f%%)", perfDiff(lastval0, v))
} }
cmpLink := fmt.Sprintf("/perfdetail?commit=%v&builder=%v&benchmark=%v", com.Hash, builder, benchmark) hh[i] = fmt.Sprintf("%v%v", v, d)
hh[i] = fmt.Sprintf("%v: <a href='%v'>%v%v</a><br><a href='%v'>%v</a><br>%v", desc, cmpLink, v, d, comLink, com.Desc, com.Time.Format("Jan 2, 2006 1:04"))
} else { } else {
hh[i] = fmt.Sprintf("%v: NO DATA<br><a href='%v'>%v</a><br>%v", desc, comLink, com.Desc, com.Time.Format("Jan 2, 2006 1:04")) hh[i] = "NO DATA"
} }
lastval = v lastval = v
if cert[i] { if cert[i] {
@ -233,7 +219,7 @@ func perfGraphHandler(w http.ResponseWriter, r *http.Request) {
vals = append(vals, valf) vals = append(vals, valf)
hints = append(hints, hh) hints = append(hints, hh)
certainty = append(certainty, cert) certainty = append(certainty, cert)
headers = append(headers, fmt.Sprintf("%s (%.2f%% [%.2f%%])", desc, diff, noise)) headers = append(headers, desc)
} }
} }
} }
@ -242,11 +228,14 @@ func perfGraphHandler(w http.ResponseWriter, r *http.Request) {
var commits []perfGraphCommit var commits []perfGraphCommit
if len(vals) != 0 && len(vals[0]) != 0 { if len(vals) != 0 && len(vals[0]) != 0 {
idx := 0
for i := range vals[0] { for i := range vals[0] {
if !commits2[i].NeedsBenchmarking { com := commits2[i]
if !com.NeedsBenchmarking {
continue continue
} }
var c perfGraphCommit c := perfGraphCommit{Id: idx, Name: fmt.Sprintf("%v (%v)", com.Desc, com.Time.Format("Jan 2, 2006 1:04"))}
idx++
for j := range vals { for j := range vals {
c.Vals = append(c.Vals, perfGraphValue{float64(vals[j][i]), certainty[j][i], hints[j][i]}) c.Vals = append(c.Vals, perfGraphValue{float64(vals[j][i]), certainty[j][i], hints[j][i]})
} }
@ -254,7 +243,7 @@ func perfGraphHandler(w http.ResponseWriter, r *http.Request) {
} }
} }
data := &perfGraphData{d, cfg, startCommit, commitsToDisplay, absolute, headers, commits} data := &perfGraphData{d, cfg, headers, commits}
var buf bytes.Buffer var buf bytes.Buffer
if err := perfGraphTemplate.Execute(&buf, data); err != nil { if err := perfGraphTemplate.Execute(&buf, data); err != nil {
@ -270,16 +259,14 @@ var perfGraphTemplate = template.Must(
) )
type perfGraphData struct { type perfGraphData struct {
Dashboard *Dashboard Dashboard *Dashboard
Config *uiPerfConfig Config *uiPerfConfig
StartCommit int Headers []string
CommitNum int Commits []perfGraphCommit
Absolute bool
Headers []string
Commits []perfGraphCommit
} }
type perfGraphCommit struct { type perfGraphCommit struct {
Id int
Name string Name string
Vals []perfGraphValue Vals []perfGraphValue
} }

View File

@ -13,15 +13,17 @@
google.setOnLoadCallback(drawCharts); google.setOnLoadCallback(drawCharts);
function drawCharts() { function drawCharts() {
var data = new google.visualization.DataTable(); var data = new google.visualization.DataTable();
data.addColumn('string', 'Commit'); data.addColumn({type: 'number', label: 'Commit'});
data.addColumn({type: 'number'});
data.addColumn({type: 'string', role: 'tooltip'});
{{range $.Headers}} {{range $.Headers}}
data.addColumn('number', '{{.}}'); data.addColumn({type: 'number', label: '{{.}}'});
data.addColumn({type: 'boolean', role: 'certainty'}); data.addColumn({type: 'boolean', role: 'certainty'});
data.addColumn({type: 'string', role: 'tooltip', p: {html: true}}); data.addColumn({type: 'string', role: 'tooltip'});
{{end}} {{end}}
data.addRows([ data.addRows([
{{range $.Commits}} {{range $.Commits}}
[ '{{.Name}}', [ {{.Id}}, 1, "{{.Name}}",
{{range .Vals}} {{range .Vals}}
{{if .Val}} {{if .Val}}
{{.Val}}, {{.Certainty}}, '{{.Hint}}', {{.Val}}, {{.Certainty}}, '{{.Hint}}',
@ -35,13 +37,12 @@
new google.visualization.LineChart(document.getElementById('graph_div')). new google.visualization.LineChart(document.getElementById('graph_div')).
draw(data, { draw(data, {
width: "100%", width: "100%",
height: 600, height: 700,
legend: {position: "bottom"}, legend: {position: "bottom"},
tooltip: {isHtml: true}, focusTarget: "category",
{{if not $.Absolute}} hAxis: {textPosition: "none"},
vAxis: {textPosition: "none", ticks: [0,10,20,30,40,50,60,70,80,90,100]}, chartArea: {left: "10%", top: "5%", width: "85%", height:"80%"},
{{end}} explorer: {axis: 'horizontal', maxZoomIn: 0, maxZoomOut: 1, zoomDelta: 1.2, keepInBounds: true}
chartArea: {left: "10%", top: "5%", width: "85%", height:"85%"}
}) })
} }
</script> </script>
@ -66,48 +67,49 @@
<form> <form>
<div class="panel"> <div class="panel">
<h1>Builders</h1> <h1>Builders</h1>
<select required multiple name="builder" size="{{len $.Config.Builders}}">
{{range $.Config.Builders}} {{range $.Config.Builders}}
<option {{if .Selected}}selected{{end}}>{{.Name}}</option> <input type="checkbox" name="builder" value="{{.Name}}" {{if .Selected}}checked{{end}}>{{.Name}}</input><br>
{{end}} {{end}}
</select>
</div> </div>
<div class="panel"> <div class="panel">
<h1>Benchmarks</h1> <h1>Benchmarks</h1>
<select required multiple name="benchmark" size="{{len $.Config.Benchmarks}}">
{{range $.Config.Benchmarks}} {{range $.Config.Benchmarks}}
<option {{if .Selected}}selected{{end}}>{{.Name}}</option> <input type="checkbox" name="benchmark" value="{{.Name}}" {{if .Selected}}checked{{end}}>{{.Name}}</input><br>
{{end}} {{end}}
</select>
</div> </div>
<div class="panel"> <div class="panel">
<h1>Procs</h1> <h1>Procs</h1>
<select required multiple name="procs" size="{{len $.Config.Procs}}">
{{range $.Config.Procs}} {{range $.Config.Procs}}
<option {{if .Selected}}selected{{end}}>{{.Name}}</option> <input type="checkbox" name="procs" value="{{.Name}}" {{if .Selected}}checked{{end}}>{{.Name}}</input><br>
{{end}} {{end}}
</select>
</div> </div>
<div class="panel"> <div class="panel">
<h1>Metrics</h1> <h1>Metrics</h1>
<select required multiple name="metric" size="{{len $.Config.Metrics}}">
{{range $.Config.Metrics}} {{range $.Config.Metrics}}
<input type="checkbox" name="metric" value="{{.Name}}" {{if .Selected}}checked{{end}}>{{.Name}}</input><br>
{{end}}
</div>
<div class="panel">
<h1>Commits</h1>
<b>From:</b>
<select required name="commit-from">
{{range $.Config.CommitsFrom}}
<option {{if .Selected}}selected{{end}}>{{.Name}}</option>
{{end}}
</select>
<b>To:</b>
<select required name="commit-to">
{{range $.Config.CommitsTo}}
<option {{if .Selected}}selected{{end}}>{{.Name}}</option> <option {{if .Selected}}selected{{end}}>{{.Name}}</option>
{{end}} {{end}}
</select> </select>
</div> </div>
<input type="checkbox" name="absolute" value="1" {{if $.Absolute}} checked {{end}} >absolute scale</input>
<input type="hidden" name="startcommit" value="{{$.StartCommit}}" />
<input type="hidden" name="commitnum" value="{{$.CommitNum}}" />
<input class="button" type="submit" value="Refresh" name="refresh"/> <input class="button" type="submit" value="Refresh" name="refresh"/>
<input class="button" type="submit" value="Zoom in" name="zoomin"/>
<input class="button" type="submit" value="Zoom out" name="zoomout"/>
<input class="button" type="submit" value="Newer" name="newer"/>
<input class="button" type="submit" value="Older" name="older"/>
</form> </form>
</aside> </aside>
<div class="clear"></div> <div class="clear"></div>