mirror of
https://github.com/golang/go
synced 2024-11-18 19:54:44 -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
@ -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 {
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
Loading…
Reference in New Issue
Block a user