Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(316)

Side by Side Diff: golden/go/skiacorrectness/main2.go

Issue 1200343002: gold: ByBlame WIP (Closed) Base URL: https://skia.googlesource.com/buildbot@master
Patch Set: rebase Created 5 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « golden/go/skiacorrectness/main.go ('k') | golden/res/imp/commit-panel.html » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 package main 1 package main
2 2
3 import ( 3 import (
4 "encoding/json" 4 "encoding/json"
5 "fmt" 5 "fmt"
6 "html/template"
6 "net/http" 7 "net/http"
7 "net/url" 8 "net/url"
8 "path/filepath" 9 "path/filepath"
9 "regexp" 10 "regexp"
10 "runtime" 11 "runtime"
11 "sort" 12 "sort"
12 "strconv" 13 "strconv"
13 » "text/template" 14 » "strings"
14 "time" 15 "time"
15 16
16 "github.com/gorilla/mux" 17 "github.com/gorilla/mux"
17 "github.com/skia-dev/glog" 18 "github.com/skia-dev/glog"
18 "go.skia.org/infra/go/human" 19 "go.skia.org/infra/go/human"
19 "go.skia.org/infra/go/login" 20 "go.skia.org/infra/go/login"
20 "go.skia.org/infra/go/timer" 21 "go.skia.org/infra/go/timer"
21 "go.skia.org/infra/go/util" 22 "go.skia.org/infra/go/util"
22 "go.skia.org/infra/golden/go/blame" 23 "go.skia.org/infra/golden/go/blame"
23 "go.skia.org/infra/golden/go/diff" 24 "go.skia.org/infra/golden/go/diff"
(...skipping 13 matching lines...) Expand all
37 // Maximum page size used for pagination. 38 // Maximum page size used for pagination.
38 MAX_PAGE_SIZE = 100 39 MAX_PAGE_SIZE = 100
39 ) 40 )
40 41
41 var ( 42 var (
42 templates *template.Template 43 templates *template.Template
43 ) 44 )
44 45
45 func loadTemplates() { 46 func loadTemplates() {
46 templates = template.Must(template.New("").Delims("{%", "%}").ParseFiles ( 47 templates = template.Must(template.New("").Delims("{%", "%}").ParseFiles (
48 filepath.Join(*resourcesDir, "templates/blamelist.html"),
49 filepath.Join(*resourcesDir, "templates/byblame.html"),
47 filepath.Join(*resourcesDir, "templates/index.html"), 50 filepath.Join(*resourcesDir, "templates/index.html"),
48 filepath.Join(*resourcesDir, "templates/ignores.html"), 51 filepath.Join(*resourcesDir, "templates/ignores.html"),
49 filepath.Join(*resourcesDir, "templates/compare.html"), 52 filepath.Join(*resourcesDir, "templates/compare.html"),
50 filepath.Join(*resourcesDir, "templates/single.html"), 53 filepath.Join(*resourcesDir, "templates/single.html"),
51 filepath.Join(*resourcesDir, "templates/diff.html"), 54 filepath.Join(*resourcesDir, "templates/diff.html"),
52 filepath.Join(*resourcesDir, "templates/help.html"), 55 filepath.Join(*resourcesDir, "templates/help.html"),
53 filepath.Join(*resourcesDir, "templates/triagelog.html"), 56 filepath.Join(*resourcesDir, "templates/triagelog.html"),
54 filepath.Join(*resourcesDir, "templates/search.html"), 57 filepath.Join(*resourcesDir, "templates/search.html"),
55 // Sub templates used by other templates. 58 // Sub templates used by other templates.
56 filepath.Join(*resourcesDir, "templates/titlebar.html"), 59 filepath.Join(*resourcesDir, "templates/titlebar.html"),
(...skipping 791 matching lines...) Expand 10 before | Expand all | Expand 10 after
848 left := r.Form.Get("left") 851 left := r.Form.Get("left")
849 if top == "" || left == "" { 852 if top == "" || left == "" {
850 util.ReportError(w, r, fmt.Errorf("Missing the top or left query parameter: %s %s", top, left), "No digests specified.") 853 util.ReportError(w, r, fmt.Errorf("Missing the top or left query parameter: %s %s", top, left), "No digests specified.")
851 return 854 return
852 } 855 }
853 test := r.Form.Get("test") 856 test := r.Form.Get("test")
854 if test == "" { 857 if test == "" {
855 util.ReportError(w, r, fmt.Errorf("Missing the test query parame ter."), "No test name specified.") 858 util.ReportError(w, r, fmt.Errorf("Missing the test query parame ter."), "No test name specified.")
856 return 859 return
857 } 860 }
861
858 exp, err := storages.ExpectationsStore.Get() 862 exp, err := storages.ExpectationsStore.Get()
859 if err != nil { 863 if err != nil {
860 util.ReportError(w, r, err, "Failed to load expectations.") 864 util.ReportError(w, r, err, "Failed to load expectations.")
861 return 865 return
862 } 866 }
863 867
864 » ret := PolyDetailsGUI{ 868 » ret := buildDetailsGUI(tile, exp, test, top, left, r.Form.Get("graphs") == "true", r.Form.Get("closest") == "true")
869
870 » w.Header().Set("Content-Type", "application/json")
871 » enc := json.NewEncoder(w)
872 » if err := enc.Encode(ret); err != nil {
873 » » glog.Errorf("Failed to write or encode result: %s", err)
874 » }
875 }
876
877 func buildDetailsGUI(tile *ptypes.Tile, exp *expstorage.Expectations, test strin g, top string, left string, graphs bool, closest bool) *PolyDetailsGUI {
878 » ret := &PolyDetailsGUI{
865 TopStatus: exp.Classification(test, top).String(), 879 TopStatus: exp.Classification(test, top).String(),
866 LeftStatus: exp.Classification(test, left).String(), 880 LeftStatus: exp.Classification(test, left).String(),
867 Params: []*PerParamCompare{}, 881 Params: []*PerParamCompare{},
868 Traces: []*Trace{}, 882 Traces: []*Trace{},
869 TileSize: len(tile.Commits), 883 TileSize: len(tile.Commits),
870 } 884 }
871 885
872 topParamSet := map[string][]string{} 886 topParamSet := map[string][]string{}
873 leftParamSet := map[string][]string{} 887 leftParamSet := map[string][]string{}
874 888
(...skipping 20 matching lines...) Expand all
895 sort.Strings(keys) 909 sort.Strings(keys)
896 for _, k := range keys { 910 for _, k := range keys {
897 ret.Params = append(ret.Params, &PerParamCompare{ 911 ret.Params = append(ret.Params, &PerParamCompare{
898 Name: k, 912 Name: k,
899 Top: safeGet(topParamSet, k), 913 Top: safeGet(topParamSet, k),
900 Left: safeGet(leftParamSet, k), 914 Left: safeGet(leftParamSet, k),
901 }) 915 })
902 } 916 }
903 917
904 // Now build the trace data. 918 // Now build the trace data.
905 » if r.Form.Get("graphs") == "true" { 919 » if graphs {
906 ret.Traces, ret.OtherDigests = buildTraceData(top, traceNames, t ile, tally, exp) 920 ret.Traces, ret.OtherDigests = buildTraceData(top, traceNames, t ile, tally, exp)
907 ret.Commits = tile.Commits 921 ret.Commits = tile.Commits
908 ret.Blame = blamer.GetBlame(test, top, ret.Commits) 922 ret.Blame = blamer.GetBlame(test, top, ret.Commits)
909 } 923 }
910 924
911 // Now find the closest positive and negative digests. 925 // Now find the closest positive and negative digests.
912 » if r.Form.Get("closest") == "true" { 926 » if closest {
913 ret.PosClosest = digesttools.ClosestDigest(test, top, exp, stora ges.DiffStore, types.POSITIVE) 927 ret.PosClosest = digesttools.ClosestDigest(test, top, exp, stora ges.DiffStore, types.POSITIVE)
914 ret.NegClosest = digesttools.ClosestDigest(test, top, exp, stora ges.DiffStore, types.NEGATIVE) 928 ret.NegClosest = digesttools.ClosestDigest(test, top, exp, stora ges.DiffStore, types.NEGATIVE)
915 } 929 }
916 930
917 » w.Header().Set("Content-Type", "application/json") 931 » return ret
918 » enc := json.NewEncoder(w)
919 » if err := enc.Encode(ret); err != nil {
920 » » glog.Errorf("Failed to write or encode result: %s", err)
921 » }
922 } 932 }
923 933
924 // digestIndex returns the index of the digest d in digestInfo, or -1 if not fou nd. 934 // digestIndex returns the index of the digest d in digestInfo, or -1 if not fou nd.
925 func digestIndex(d string, digestInfo []*DigestStatus) int { 935 func digestIndex(d string, digestInfo []*DigestStatus) int {
926 for i, di := range digestInfo { 936 for i, di := range digestInfo {
927 if di.Digest == d { 937 if di.Digest == d {
928 return i 938 return i
929 } 939 }
930 } 940 }
931 return -1 941 return -1
(...skipping 106 matching lines...) Expand 10 before | Expand all | Expand 10 after
1038 } 1048 }
1039 } 1049 }
1040 } 1050 }
1041 1051
1042 // polyStatusHandler returns the current status of with respect to 1052 // polyStatusHandler returns the current status of with respect to
1043 // HEAD. 1053 // HEAD.
1044 func polyStatusHandler(w http.ResponseWriter, r *http.Request) { 1054 func polyStatusHandler(w http.ResponseWriter, r *http.Request) {
1045 sendJsonResponse(w, statusWatcher.GetStatus()) 1055 sendJsonResponse(w, statusWatcher.GetStatus())
1046 } 1056 }
1047 1057
1058 // allUntriagedSummaries returns a tile and summaries for all untriaged GMs.
1059 //
1060 // TODO(jcgregorio) Make source_type selectable.
1061 func allUntriagedSummaries() (*ptypes.Tile, map[string]*summary.Summary, error) {
1062 tile, err := storages.GetLastTileTrimmed(true)
1063 if err != nil {
1064 return nil, nil, fmt.Errorf("Couldn't load tile: %s", err)
1065 }
1066 // Get a list of all untriaged images by test.
1067 sum, err := summaries.CalcSummaries([]string{}, "source_type=gm", false, true)
1068 if err != nil {
1069 return nil, nil, fmt.Errorf("Couldn't load summaries: %s", err)
1070 }
1071 return tile, sum, nil
1072 }
1073
1074 // ByBlame describes a single digest and it's blames.
1075 type ByBlame struct {
1076 Test string `json:"test"`
1077 Digest string `json:"digest"`
1078 Blame *blame.BlameDistribution `json:"blame"`
1079 CommitIndices []int `json:"commit_indices"`
1080 Key string
1081 }
1082
1083 // lookUpCommits returns the commit hashes for the commit indices in 'freq'.
1084 func lookUpCommits(freq []int, commits []*ptypes.Commit) []string {
1085 ret := []string{}
1086 for _, index := range freq {
1087 ret = append(ret, commits[index].Hash)
1088 }
1089 return ret
1090 }
1091
1092 // byBlameHandler returns a page with the digests to be triaged grouped by blame list.
1093 func byBlameHandler(w http.ResponseWriter, r *http.Request) {
1094 w.Header().Set("Content-Type", "text/html")
1095 if *local {
1096 loadTemplates()
1097 }
1098 tile, sum, err := allUntriagedSummaries()
1099 commits := tile.Commits
1100 if err != nil {
1101 util.ReportError(w, r, err, "Failed to load summaries.")
1102 return
1103 }
1104
1105 // This is a very simple grouping of digests, for every digest we look u p the
1106 // blame list for that digest and then use the concatenated git hashes a s a
1107 // group id. All of the digests are then grouped by their group id.
1108
1109 // Collects a ByBlame for each untriaged digest, keyed by group id.
1110 grouped := map[string][]*ByBlame{}
1111
1112 // The Commit info for each group id.
1113 commitinfo := map[string][]*ptypes.Commit{}
1114 for test, s := range sum {
1115 for _, d := range s.UntHashes {
1116 dist := blamer.GetBlame(test, d, commits)
1117 groupid := strings.Join(lookUpCommits(dist.Freq, commits ), ":")
1118 // Only fill in commitinfo for each groupid only once.
1119 if _, ok := commitinfo[groupid]; !ok {
1120 ci := []*ptypes.Commit{}
1121 for _, index := range dist.Freq {
1122 ci = append(ci, commits[index])
1123 }
1124 commitinfo[groupid] = ci
1125 }
1126 // Construct a ByBlame and add it to grouped.
1127 value := &ByBlame{
1128 Test: test,
1129 Digest: d,
1130 Blame: dist,
1131 CommitIndices: dist.Freq,
1132 }
1133 if _, ok := grouped[groupid]; !ok {
1134 grouped[groupid] = []*ByBlame{value}
1135 } else {
1136 grouped[groupid] = append(grouped[groupid], valu e)
1137 }
1138 }
1139 }
1140
1141 // The Commit info needs to be accessed via Javascript, so serialize it into
1142 // JSON here.
1143 commitinfojs, err := json.MarshalIndent(commitinfo, "", " ")
1144 if err != nil {
1145 util.ReportError(w, r, err, "Failed to encode response data.")
1146 return
1147 }
1148
1149 keys := []string{}
1150 for groupid, _ := range grouped {
1151 keys = append(keys, groupid)
1152 }
1153 sort.Strings(keys)
1154
1155 if err := templates.ExecuteTemplate(w, "byblame.html",
1156 struct {
1157 Keys []string
1158 ByBlame map[string][]*ByBlame
1159 CommitsJS template.JS
1160 }{
1161 Keys: keys,
1162 ByBlame: grouped,
1163 CommitsJS: template.JS(string(commitinfojs)),
1164 }); err != nil {
1165 glog.Errorln("Failed to expand template:", err)
1166 }
1167 }
1168
1169 func minFloat32(a, b float32) float32 {
1170 if a < b {
1171 return a
1172 }
1173 return b
1174 }
1175
1176 // BlameListDigest holds a PolyDetailsGUI and some other info needed to render
1177 // a test-summary-details-sk.
1178 type BlameListDigest struct {
1179 Test string `json:"test"`
1180 Digest string `json:"digest"`
1181 Diff float32 `json:"diff"` // The smaller of the Pos and Neg d iff.
1182 Detail *PolyDetailsGUI `json:"detail"`
1183 }
1184
1185 // BlameListDigestSlice enables sorting BlameListDigest by the Diff score, with
1186 // largest Diff's first.
1187 type BlameListDigestSlice []*BlameListDigest
1188
1189 func (p BlameListDigestSlice) Len() int { return len(p) }
1190 func (p BlameListDigestSlice) Less(i, j int) bool { return p[i].Diff > p[j].Diff }
1191 func (p BlameListDigestSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
1192
1193 // blameListHandler returns a page for triaging all digests that match the give blame group id.
1194 //
1195 // Links to this page come from the byBlameHandler page.
1196 //
1197 // Request URLs query parameters:
1198 // groupid - The blame list group id, which is just the list of git hashes
1199 // that are in the blame concatenated with ':'s.
1200 func blameListHandler(w http.ResponseWriter, r *http.Request) {
1201 w.Header().Set("Content-Type", "text/html")
1202 if *local {
1203 loadTemplates()
1204 }
1205 tile, sum, err := allUntriagedSummaries()
1206 if err != nil {
1207 util.ReportError(w, r, err, "Failed to load summaries.")
1208 return
1209 }
1210 commits := tile.Commits
1211 exp, err := storages.ExpectationsStore.Get()
1212 if err != nil {
1213 util.ReportError(w, r, err, "Failed to load expectations.")
1214 return
1215 }
1216 if err := r.ParseForm(); err != nil {
1217 util.ReportError(w, r, err, "Failed to parse form data.")
1218 return
1219 }
1220 groupid := r.Form.Get("groupid")
1221
1222 // Populate a slice of BlameListDigest's that match the given blame list .
1223 list := []*BlameListDigest{}
1224 for test, s := range sum {
1225 for _, d := range s.UntHashes {
1226 dist := blamer.GetBlame(test, d, commits)
1227 key := strings.Join(lookUpCommits(dist.Freq, commits), " :")
1228 if key == groupid {
1229 detail := buildDetailsGUI(tile, exp, test, d, d, true, true)
1230
1231 list = append(list, &BlameListDigest{
1232 Test: test,
1233 Digest: d,
1234 Diff: minFloat32(detail.NegClosest.Dif f, detail.PosClosest.Diff),
1235 Detail: detail,
1236 })
1237 }
1238 }
1239 }
1240
1241 sort.Sort(BlameListDigestSlice(list))
1242
1243 // The list needs to be available both via golang templates and also
1244 // in Javascript, so we need to encode the list to JSON so it can
1245 // appear in JS.
1246 js, err := json.MarshalIndent(list, "", " ")
1247 if err != nil {
1248 util.ReportError(w, r, err, "Failed to encode response data.")
1249 return
1250 }
1251
1252 context := struct {
1253 Summaries []*BlameListDigest
1254 JS template.JS
1255 }{
1256 Summaries: list,
1257 JS: template.JS(string(js)),
1258 }
1259
1260 if err := templates.ExecuteTemplate(w, "blamelist.html", context); err ! = nil {
1261 glog.Errorln("Failed to expand template:", err)
1262 }
1263 }
1264
1048 // sendJsonResponse serializes resp to JSON. If an error occurs 1265 // sendJsonResponse serializes resp to JSON. If an error occurs
1049 // a text based error code is send to the client. 1266 // a text based error code is send to the client.
1050 func sendJsonResponse(w http.ResponseWriter, resp interface{}) { 1267 func sendJsonResponse(w http.ResponseWriter, resp interface{}) {
1051 w.Header().Set("Content-Type", "application/json") 1268 w.Header().Set("Content-Type", "application/json")
1052 if err := json.NewEncoder(w).Encode(resp); err != nil { 1269 if err := json.NewEncoder(w).Encode(resp); err != nil {
1053 glog.Errorf("Failed to write or encode result: %s", err) 1270 glog.Errorf("Failed to write or encode result: %s", err)
1054 } 1271 }
1055 } 1272 }
1056 1273
1057 // makeResourceHandler creates a static file handler that sets a caching policy. 1274 // makeResourceHandler creates a static file handler that sets a caching policy.
1058 func makeResourceHandler() func(http.ResponseWriter, *http.Request) { 1275 func makeResourceHandler() func(http.ResponseWriter, *http.Request) {
1059 fileServer := http.FileServer(http.Dir(*resourcesDir)) 1276 fileServer := http.FileServer(http.Dir(*resourcesDir))
1060 return func(w http.ResponseWriter, r *http.Request) { 1277 return func(w http.ResponseWriter, r *http.Request) {
1061 w.Header().Add("Cache-Control", string(300)) 1278 w.Header().Add("Cache-Control", string(300))
1062 fileServer.ServeHTTP(w, r) 1279 fileServer.ServeHTTP(w, r)
1063 } 1280 }
1064 } 1281 }
1065 1282
1066 // Init figures out where the resources are and then loads the templates. 1283 // Init figures out where the resources are and then loads the templates.
1067 func Init() { 1284 func Init() {
1068 if *resourcesDir == "" { 1285 if *resourcesDir == "" {
1069 _, filename, _, _ := runtime.Caller(0) 1286 _, filename, _, _ := runtime.Caller(0)
1070 *resourcesDir = filepath.Join(filepath.Dir(filename), "../..") 1287 *resourcesDir = filepath.Join(filepath.Dir(filename), "../..")
1071 } 1288 }
1072 loadTemplates() 1289 loadTemplates()
1073 } 1290 }
OLDNEW
« no previous file with comments | « golden/go/skiacorrectness/main.go ('k') | golden/res/imp/commit-panel.html » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698