1
0
mirror of https://github.com/golang/go synced 2024-11-18 17:14:45 -07: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

@ -211,6 +211,8 @@ type uiPerfConfig struct {
Benchmarks []uiPerfConfigElem
Metrics []uiPerfConfigElem
Procs []uiPerfConfigElem
CommitsFrom []uiPerfConfigElem
CommitsTo []uiPerfConfigElem
}
type uiPerfConfigElem struct {

View File

@ -36,7 +36,6 @@ func perfGraphHandler(w http.ResponseWriter, r *http.Request) {
allMetrics := pc.MetricsForBenchmark("")
allProcs := pc.ProcList("")
r.ParseForm()
absolute := r.FormValue("absolute") != ""
selBuilders := r.Form["builder"]
selBenchmarks := r.Form["benchmark"]
selMetrics := r.Form["metric"]
@ -53,8 +52,55 @@ func perfGraphHandler(w http.ResponseWriter, r *http.Request) {
if len(selProcs) == 0 {
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
// 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 {
for _, s1 := range set {
if s1 == s {
@ -77,43 +123,14 @@ func perfGraphHandler(w http.ResponseWriter, r *http.Request) {
for _, v := range allProcs {
cfg.Procs = append(cfg.Procs, uiPerfConfigElem{strconv.Itoa(v), present(selProcs, strconv.Itoa(v))})
}
for k := range knownTags {
cfg.CommitsFrom = append(cfg.CommitsFrom, uiPerfConfigElem{k, commitFrom == k})
}
for k := range knownTags {
cfg.CommitsTo = append(cfg.CommitsTo, uiPerfConfigElem{k, commitTo == k})
}
cfg.CommitsTo = append(cfg.CommitsTo, uiPerfConfigElem{"tip", commitTo == "tip"})
// Select last commit.
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
}
if r.FormValue("zoomin") != "" {
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
}
// TODO(dvyukov): limit number of lines on the graph?
startCommitNum := startCommit - commitsToDisplay + 1
if startCommitNum < 0 {
startCommitNum = 0
}
var vals [][]float64
var hints [][]string
var certainty [][]bool
@ -133,39 +150,13 @@ func perfGraphHandler(w http.ResponseWriter, r *http.Request) {
logErr(w, r, err)
return
}
nonzero := false
min := ^uint64(0)
max := uint64(0)
hasdata := false
for _, v := range vv {
if v == 0 {
continue
if v != 0 {
hasdata = true
}
if max < v {
max = v
}
if min > v {
min = v
}
nonzero = true
}
if nonzero {
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
}
if hasdata {
descBuilder := "/" + builder
descBenchmark := "/" + benchProcs
descMetric := "/" + metric
@ -182,6 +173,7 @@ func perfGraphHandler(w http.ResponseWriter, r *http.Request) {
hh := make([]string, commitsToDisplay)
valf := make([]float64, commitsToDisplay)
cert := make([]bool, commitsToDisplay)
firstval := uint64(0)
lastval := uint64(0)
lastval0 := uint64(0)
for i, v := range vv {
@ -204,26 +196,20 @@ func perfGraphHandler(w http.ResponseWriter, r *http.Request) {
}
cert[i] = false
v = lastval + uint64(int64(nextval-lastval)/int64(nextidx-i+1))
_, _ = nextval, nextidx
}
f := float64(v)
if !absolute {
f = (float64(v) - float64(min)) * 100 / (float64(max) - float64(min))
f = f*scale/100 + (100-scale)/2
f += 0.000001
if firstval == 0 {
firstval = v
}
valf[i] = f
com := commits2[i]
comLink := "https://code.google.com/p/go/source/detail?r=" + com.Hash
valf[i] = float64(v) / float64(firstval)
if cert[i] {
d := ""
if lastval0 != 0 {
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: <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"))
hh[i] = fmt.Sprintf("%v%v", v, d)
} 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
if cert[i] {
@ -233,7 +219,7 @@ func perfGraphHandler(w http.ResponseWriter, r *http.Request) {
vals = append(vals, valf)
hints = append(hints, hh)
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
if len(vals) != 0 && len(vals[0]) != 0 {
idx := 0
for i := range vals[0] {
if !commits2[i].NeedsBenchmarking {
com := commits2[i]
if !com.NeedsBenchmarking {
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 {
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
if err := perfGraphTemplate.Execute(&buf, data); err != nil {
@ -272,14 +261,12 @@ var perfGraphTemplate = template.Must(
type perfGraphData struct {
Dashboard *Dashboard
Config *uiPerfConfig
StartCommit int
CommitNum int
Absolute bool
Headers []string
Commits []perfGraphCommit
}
type perfGraphCommit struct {
Id int
Name string
Vals []perfGraphValue
}

View File

@ -13,15 +13,17 @@
google.setOnLoadCallback(drawCharts);
function drawCharts() {
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}}
data.addColumn('number', '{{.}}');
data.addColumn({type: 'number', label: '{{.}}'});
data.addColumn({type: 'boolean', role: 'certainty'});
data.addColumn({type: 'string', role: 'tooltip', p: {html: true}});
data.addColumn({type: 'string', role: 'tooltip'});
{{end}}
data.addRows([
{{range $.Commits}}
[ '{{.Name}}',
[ {{.Id}}, 1, "{{.Name}}",
{{range .Vals}}
{{if .Val}}
{{.Val}}, {{.Certainty}}, '{{.Hint}}',
@ -35,13 +37,12 @@
new google.visualization.LineChart(document.getElementById('graph_div')).
draw(data, {
width: "100%",
height: 600,
height: 700,
legend: {position: "bottom"},
tooltip: {isHtml: true},
{{if not $.Absolute}}
vAxis: {textPosition: "none", ticks: [0,10,20,30,40,50,60,70,80,90,100]},
{{end}}
chartArea: {left: "10%", top: "5%", width: "85%", height:"85%"}
focusTarget: "category",
hAxis: {textPosition: "none"},
chartArea: {left: "10%", top: "5%", width: "85%", height:"80%"},
explorer: {axis: 'horizontal', maxZoomIn: 0, maxZoomOut: 1, zoomDelta: 1.2, keepInBounds: true}
})
}
</script>
@ -66,48 +67,49 @@
<form>
<div class="panel">
<h1>Builders</h1>
<select required multiple name="builder" size="{{len $.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}}
</select>
</div>
<div class="panel">
<h1>Benchmarks</h1>
<select required multiple name="benchmark" size="{{len $.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}}
</select>
</div>
<div class="panel">
<h1>Procs</h1>
<select required multiple name="procs" size="{{len $.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}}
</select>
</div>
<div class="panel">
<h1>Metrics</h1>
<select required multiple name="metric" size="{{len $.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>
{{end}}
</select>
</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="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>
</aside>
<div class="clear"></div>