// Copyright 2013 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. // +build appengine package build import ( "bytes" "fmt" "html/template" "net/http" "strconv" "appengine" "appengine/datastore" ) func init() { for _, d := range dashboards { http.HandleFunc(d.RelPath+"perfgraph", perfGraphHandler) } } func perfGraphHandler(w http.ResponseWriter, r *http.Request) { d := dashboardForRequest(r) c := d.Context(appengine.NewContext(r)) pc, err := GetPerfConfig(c, r) if err != nil { logErr(w, r, err) return } allBuilders := pc.BuildersForBenchmark("") allBenchmarks := pc.BenchmarksForBuilder("") allMetrics := pc.MetricsForBenchmark("") allProcs := pc.ProcList("") r.ParseForm() absolute := r.FormValue("absolute") != "" selBuilders := r.Form["builder"] selBenchmarks := r.Form["benchmark"] selMetrics := r.Form["metric"] selProcs := r.Form["procs"] if len(selBuilders) == 0 { selBuilders = append(selBuilders, allBuilders[0]) } if len(selBenchmarks) == 0 { selBenchmarks = append(selBenchmarks, "json") } if len(selMetrics) == 0 { selMetrics = append(selMetrics, "time") } if len(selProcs) == 0 { selProcs = append(selProcs, "1") } // TODO(dvyukov): validate input present := func(set []string, s string) bool { for _, s1 := range set { if s1 == s { return true } } return false } cfg := &uiPerfConfig{} for _, v := range allBuilders { cfg.Builders = append(cfg.Builders, uiPerfConfigElem{v, present(selBuilders, v)}) } for _, v := range allBenchmarks { cfg.Benchmarks = append(cfg.Benchmarks, uiPerfConfigElem{v, present(selBenchmarks, v)}) } for _, v := range allMetrics { cfg.Metrics = append(cfg.Metrics, uiPerfConfigElem{v, present(selMetrics, v)}) } for _, v := range allProcs { cfg.Procs = append(cfg.Procs, uiPerfConfigElem{strconv.Itoa(v), present(selProcs, strconv.Itoa(v))}) } // 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 var headers []string commits2, err := GetCommits(c, startCommitNum, commitsToDisplay) if err != nil { logErr(w, r, err) return } for _, builder := range selBuilders { for _, metric := range selMetrics { for _, benchmark := range selBenchmarks { for _, procs := range selProcs { benchProcs := fmt.Sprintf("%v-%v", benchmark, procs) vv, err := GetPerfMetricsForCommits(c, builder, benchProcs, metric, startCommitNum, commitsToDisplay) if err != nil { logErr(w, r, err) return } nonzero := false min := ^uint64(0) max := uint64(0) for _, v := range vv { if v == 0 { continue } 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 } descBuilder := "/" + builder descBenchmark := "/" + benchProcs descMetric := "/" + metric if len(selBuilders) == 1 { descBuilder = "" } if len(selBenchmarks) == 1 && len(selProcs) == 1 { descBenchmark = "" } if len(selMetrics) == 1 && (len(selBuilders) > 1 || len(selBenchmarks) > 1 || len(selProcs) > 1) { descMetric = "" } desc := fmt.Sprintf("%v%v%v", descBuilder, descBenchmark, descMetric)[1:] hh := make([]string, commitsToDisplay) valf := make([]float64, commitsToDisplay) cert := make([]bool, commitsToDisplay) lastval := uint64(0) lastval0 := uint64(0) for i, v := range vv { cert[i] = true if v == 0 { if lastval == 0 { continue } nextval := uint64(0) nextidx := 0 for i2, v2 := range vv[i+1:] { if v2 != 0 { nextval = v2 nextidx = i + i2 + 1 break } } if nextval == 0 { continue } 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 } valf[i] = f com := commits2[i] comLink := "https://code.google.com/p/go/source/detail?r=" + com.Hash 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: %v%v
%v
%v", desc, cmpLink, v, d, comLink, com.Desc, com.Time.Format("Jan 2, 2006 1:04")) } else { hh[i] = fmt.Sprintf("%v: NO DATA
%v
%v", desc, comLink, com.Desc, com.Time.Format("Jan 2, 2006 1:04")) } lastval = v if cert[i] { lastval0 = v } } vals = append(vals, valf) hints = append(hints, hh) certainty = append(certainty, cert) headers = append(headers, fmt.Sprintf("%s (%.2f%% [%.2f%%])", desc, diff, noise)) } } } } } var commits []perfGraphCommit if len(vals) != 0 && len(vals[0]) != 0 { for i := range vals[0] { if !commits2[i].NeedsBenchmarking { continue } var c perfGraphCommit for j := range vals { c.Vals = append(c.Vals, perfGraphValue{float64(vals[j][i]), certainty[j][i], hints[j][i]}) } commits = append(commits, c) } } data := &perfGraphData{d, cfg, startCommit, commitsToDisplay, absolute, headers, commits} var buf bytes.Buffer if err := perfGraphTemplate.Execute(&buf, data); err != nil { logErr(w, r, err) return } buf.WriteTo(w) } var perfGraphTemplate = template.Must( template.New("perf_graph.html").ParseFiles("build/perf_graph.html"), ) type perfGraphData struct { Dashboard *Dashboard Config *uiPerfConfig StartCommit int CommitNum int Absolute bool Headers []string Commits []perfGraphCommit } type perfGraphCommit struct { Name string Vals []perfGraphValue } type perfGraphValue struct { Val float64 Certainty bool Hint string }