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:
parent
4201ff03b5
commit
3eede9be08
@ -211,6 +211,8 @@ type uiPerfConfig struct {
|
||||
Benchmarks []uiPerfConfigElem
|
||||
Metrics []uiPerfConfigElem
|
||||
Procs []uiPerfConfigElem
|
||||
CommitsFrom []uiPerfConfigElem
|
||||
CommitsTo []uiPerfConfigElem
|
||||
}
|
||||
|
||||
type uiPerfConfigElem struct {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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>
|
||||
|
Loading…
Reference in New Issue
Block a user