diff options
| author | Dmitry Vyukov <dvyukov@google.com> | 2024-05-27 16:55:58 +0200 |
|---|---|---|
| committer | Dmitry Vyukov <dvyukov@google.com> | 2024-05-31 10:18:05 +0000 |
| commit | 4a71ac623f5560d8fc83f0840f7dded5775976ac (patch) | |
| tree | 58efe436722445c3dbb69e44bf79970a25e02db8 | |
| parent | 0c378259b1aa20c6bed6c2efd19198c0303bd18b (diff) | |
dashboard/app: add bugs found per month graph
Useful to estimate overall syzbot performance.
| -rw-r--r-- | dashboard/app/graph_bugs.html | 2 | ||||
| -rw-r--r-- | dashboard/app/graph_crashes.html | 2 | ||||
| -rw-r--r-- | dashboard/app/graph_found_bugs.html | 44 | ||||
| -rw-r--r-- | dashboard/app/graph_fuzzing.html | 2 | ||||
| -rw-r--r-- | dashboard/app/graphs.go | 136 | ||||
| -rw-r--r-- | dashboard/app/graphs_test.go | 5 | ||||
| -rw-r--r-- | dashboard/app/main.go | 1 | ||||
| -rw-r--r-- | dashboard/app/templates.html | 2 |
8 files changed, 175 insertions, 19 deletions
diff --git a/dashboard/app/graph_bugs.html b/dashboard/app/graph_bugs.html index 55ce0dbab..03342614d 100644 --- a/dashboard/app/graph_bugs.html +++ b/dashboard/app/graph_bugs.html @@ -18,7 +18,7 @@ Bugs statistics graph. function drawCharts() { new google.visualization.LineChart(document.getElementById('graph_div')). draw(google.visualization.arrayToDataTable([ - ["-", {{range $.Graph.Headers}}"{{.}}", {{end}}], + ["-", {{range $.Graph.Headers}}"{{.Name}}", {{end}}], {{range $.Graph.Columns}}["{{.Hint}}", {{range .Vals}}{{.Val}},{{end}}],{{end}} ]), { width: "100%", diff --git a/dashboard/app/graph_crashes.html b/dashboard/app/graph_crashes.html index 3e949af15..e44968154 100644 --- a/dashboard/app/graph_crashes.html +++ b/dashboard/app/graph_crashes.html @@ -20,7 +20,7 @@ Manager statistics graphs. var data = new google.visualization.DataTable(); data.addColumn({type: 'string'}); {{range $.Graph.Headers}} - data.addColumn({type: 'number', label: '{{.}}'}); + data.addColumn({type: 'number', label: '{{.Name}}'}); data.addColumn({type: 'string', role: 'tooltip'}); {{- end}} data.addRows([ {{range $.Graph.Columns}} diff --git a/dashboard/app/graph_found_bugs.html b/dashboard/app/graph_found_bugs.html new file mode 100644 index 000000000..4bf29434b --- /dev/null +++ b/dashboard/app/graph_found_bugs.html @@ -0,0 +1,44 @@ +{{/* +Copyright 2024 syzkaller project authors. All rights reserved. +Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. + +Number of found bugs per month. +*/}} + +<!doctype html> +<html> +<head> + <title>{{.Header.Namespace}} bugs found per month</title> + {{template "head" .Header}} + + <script type="text/javascript" src="https://www.google.com/jsapi"></script> + <script type="text/javascript"> + google.load("visualization", "1", {packages:["corechart"]}); + google.setOnLoadCallback(function() { + new google.visualization.ColumnChart ( + document.getElementById('graph_div')). + draw(google.visualization.arrayToDataTable([ + ["-", {{range $.Graph.Headers}}"{{.Name}}", {{end}}], + {{range $.Graph.Columns}}["{{.Hint}}", {{range .Vals}}{{.Val}},{{end}}],{{end}} + ]), { + width: "100%", + chartArea: {width: '90%', height: '85%'}, + legend: {position: 'in'}, + focusTarget: "category", + isStacked: true, + vAxis: {minValue: 1, textPosition: 'out', gridlines: {multiple: 1}, minorGridlines: {multiple: 1}}, + hAxis: {minValue: 1, textPosition: 'out', maxAlternation: 1, gridlines: {multiple: 1}, minorGridlines: {multiple: 1}}, + series: { + {{range $idx, $hdr := $.Graph.Headers}} + {{$idx}}: {color: "{{$hdr.Color}}"}, + {{end}} + }, + }) + }); + </script> +</head> +<body> + {{template "header" .Header}} + <div id="graph_div"></div> +</body> +</html> diff --git a/dashboard/app/graph_fuzzing.html b/dashboard/app/graph_fuzzing.html index 5ed07ba63..d7a22e6a6 100644 --- a/dashboard/app/graph_fuzzing.html +++ b/dashboard/app/graph_fuzzing.html @@ -19,7 +19,7 @@ Manager statistics graphs. var data = new google.visualization.DataTable(); data.addColumn({type: 'string'}); {{range $.Graph.Headers}} - data.addColumn({type: 'number', label: '{{.}}'}); + data.addColumn({type: 'number', label: '{{.Name}}'}); data.addColumn({type: 'string', role: 'tooltip'}); {{- end}} data.addRows([ {{range $.Graph.Columns}} diff --git a/dashboard/app/graphs.go b/dashboard/app/graphs.go index f4789b682..a0f48f312 100644 --- a/dashboard/app/graphs.go +++ b/dashboard/app/graphs.go @@ -26,6 +26,11 @@ type uiBugLifetimesPage struct { Lifetimes []uiBugLifetime } +type uiReportedBugsPage struct { + Header *uiHeader + Graph *uiGraph +} + type uiBugLifetime struct { Reported time.Time Fixed float32 @@ -53,10 +58,15 @@ type uiCrashesPage struct { } type uiGraph struct { - Headers []string + Headers []uiGraphHeader Columns []uiGraphColumn } +type uiGraphHeader struct { + Name string + Color string +} + type uiGraphColumn struct { Hint string Vals []uiGraphValue @@ -114,7 +124,7 @@ func handleKernelHealthGraph(c context.Context, w http.ResponseWriter, r *http.R if err != nil { return err } - bugs, err := loadGraphBugs(c, hdr.Namespace) + bugs, err := loadGraphBugs(c, hdr.Namespace, true) if err != nil { return err } @@ -131,7 +141,7 @@ func handleGraphLifetimes(c context.Context, w http.ResponseWriter, r *http.Requ if err != nil { return err } - bugs, err := loadGraphBugs(c, hdr.Namespace) + bugs, err := loadGraphBugs(c, hdr.Namespace, true) if err != nil { return err } @@ -158,7 +168,24 @@ func handleGraphLifetimes(c context.Context, w http.ResponseWriter, r *http.Requ return serveTemplate(w, "graph_lifetimes.html", data) } -func loadGraphBugs(c context.Context, ns string) ([]*Bug, error) { +// nolint: dupl +func handleFoundBugsGraph(c context.Context, w http.ResponseWriter, r *http.Request) error { + hdr, err := commonHeader(c, r, w, "") + if err != nil { + return err + } + bugs, err := loadGraphBugs(c, hdr.Namespace, false) + if err != nil { + return err + } + data := &uiReportedBugsPage{ + Header: hdr, + Graph: createFoundBugs(c, bugs), + } + return serveTemplate(w, "graph_found_bugs.html", data) +} + +func loadGraphBugs(c context.Context, ns string, removeDupInvalid bool) ([]*Bug, error) { filter := func(query *db.Query) *db.Query { return query.Filter("Namespace=", ns) } @@ -176,20 +203,23 @@ func loadGraphBugs(c context.Context, ns string) ([]*Bug, error) { continue } bugReporting := lastReportedReporting(bug) - if bugReporting == nil || bugReporting.Auto && bug.Status == BugStatusInvalid { + if removeDupInvalid && + (bugReporting == nil || bugReporting.Auto && bug.Status == BugStatusInvalid) { // These bugs were auto-obsoleted before getting released. continue } } - dup := false - for _, com := range bug.Commits { - if fixes[com] { - dup = true + if removeDupInvalid { + dup := false + for _, com := range bug.Commits { + if fixes[com] { + dup = true + } + fixes[com] = true + } + if dup { + continue } - fixes[com] = true - } - if dup { - continue } bugs[n] = bug n++ @@ -263,7 +293,78 @@ func createBugsGraph(c context.Context, bugs []*Bug) *uiGraph { columns = append(columns, col) } return &uiGraph{ - Headers: []string{"open bugs", "total reported", "total fixed"}, + Headers: []uiGraphHeader{{Name: "open bugs"}, {Name: "total reported"}, {Name: "total fixed"}}, + Columns: columns, + } +} + +func createFoundBugs(c context.Context, bugs []*Bug) *uiGraph { + const projected = "projected" + // This is linux-specific at the moment, potentially can move to pkg/report/crash + // and extend to other OSes. + // nolint: lll + types := []struct { + name string + color string + re *regexp.Regexp + }{ + {"KASAN", "Red", regexp.MustCompile(`^KASAN:`)}, + {"KMSAN", "Gold", regexp.MustCompile(`^KMSAN:`)}, + {"KCSAN", "Fuchsia", regexp.MustCompile(`^KCSAN:`)}, + {"mem safety", "OrangeRed", regexp.MustCompile(`^(WARNING: refcount bug|UBSAN: array-index|BUG: corrupted list|BUG: unable to handle kernel paging request)`)}, + {"mem leak", "MediumSeaGreen", regexp.MustCompile(`^memory leak`)}, + {"locking", "DodgerBlue", regexp.MustCompile(`^(BUG: sleeping function|BUG: spinlock recursion|BUG: using ([a-z_]+)\\(\\) in preemptible|inconsistent lock state|WARNING: still has locks held|possible deadlock|WARNING: suspicious RCU usage)`)}, + {"hangs/stalls", "LightSalmon", regexp.MustCompile(`^(BUG: soft lockup|INFO: rcu .* stall|INFO: task hung)`)}, + // This must be at the end, otherwise "BUG:" will match other error types. + {"DoS", "Violet", regexp.MustCompile(`^(BUG:|kernel BUG|divide error|Internal error in|kernel panic:|general protection fault)`)}, + {"other", "Gray", regexp.MustCompile(`.*`)}, + {projected, "LightGray", nil}, + } + var sorted []time.Time + months := make(map[time.Time]map[string]int) + for _, bug := range bugs { + for _, typ := range types { + if !typ.re.MatchString(bug.Title) { + continue + } + t := bug.FirstTime + m := time.Date(t.Year(), t.Month(), 0, 0, 0, 0, 0, time.UTC) + if months[m] == nil { + months[m] = make(map[string]int) + sorted = append(sorted, m) + } + months[m][typ.name]++ + break + } + } + sort.Slice(sorted, func(i, j int) bool { + return sorted[i].Before(sorted[j]) + }) + now := timeNow(c) + thisMonth := time.Date(now.Year(), now.Month(), 0, 0, 0, 0, 0, time.UTC) + if m := months[thisMonth]; m != nil { + total := 0 + for _, c := range m { + total += c + } + nextMonth := thisMonth.AddDate(0, 1, 0) + m[projected] = int(float64(total) / float64(now.Sub(thisMonth)) * float64(nextMonth.Sub(now))) + } + var headers []uiGraphHeader + for _, typ := range types { + headers = append(headers, uiGraphHeader{Name: typ.name, Color: typ.color}) + } + var columns []uiGraphColumn + for _, month := range sorted { + col := uiGraphColumn{Hint: month.Format("Jan-06")} + stats := months[month] + for _, typ := range types { + col.Vals = append(col.Vals, uiGraphValue{Val: float32(stats[typ.name])}) + } + columns = append(columns, col) + } + return &uiGraph{ + Headers: headers, Columns: columns, } } @@ -361,7 +462,7 @@ func createManagersGraph(c context.Context, ns string, selManagers, selMetrics [ graph := &uiGraph{} for _, mgr := range selManagers { for _, metric := range selMetrics { - graph.Headers = append(graph.Headers, mgr+"-"+metric) + graph.Headers = append(graph.Headers, uiGraphHeader{Name: mgr + "-" + metric}) } } now := timeNow(c) @@ -603,7 +704,10 @@ func createCrashesTable(c context.Context, ns string, days int, bugs []*Bug) *ui func createCrashesGraph(c context.Context, ns string, regexps []string, days int, bugs []*Bug) (*uiGraph, error) { const dayDuration = 24 * time.Hour - graph := &uiGraph{Headers: regexps} + graph := &uiGraph{} + for _, re := range regexps { + graph.Headers = append(graph.Headers, uiGraphHeader{Name: re}) + } startDay := timeNow(c).Add(time.Duration(-days) * dayDuration) // Step 1: fill the whole table with empty values. dateToIdx := make(map[int]int) diff --git a/dashboard/app/graphs_test.go b/dashboard/app/graphs_test.go index 57aba18de..2e1391ed0 100644 --- a/dashboard/app/graphs_test.go +++ b/dashboard/app/graphs_test.go @@ -110,6 +110,11 @@ func TestManagersGraphs(t *testing.T) { c.expectOK(err) // TODO: check reply _ = reply + + reply, err = c.AuthGET(AccessAdmin, "/test2/graph/found-bugs") + c.expectOK(err) + // TODO: check reply + _ = reply } func managersGraphFixture(t *testing.T) *Ctx { diff --git a/dashboard/app/main.go b/dashboard/app/main.go index 2ac74af27..4e26fb86b 100644 --- a/dashboard/app/main.go +++ b/dashboard/app/main.go @@ -63,6 +63,7 @@ func initHTTPHandlers() { http.Handle("/"+ns+"/graph/lifetimes", handlerWrapper(handleGraphLifetimes)) http.Handle("/"+ns+"/graph/fuzzing", handlerWrapper(handleGraphFuzzing)) http.Handle("/"+ns+"/graph/crashes", handlerWrapper(handleGraphCrashes)) + http.Handle("/"+ns+"/graph/found-bugs", handlerWrapper(handleFoundBugsGraph)) http.Handle("/"+ns+"/repos", handlerWrapper(handleRepos)) http.Handle("/"+ns+"/bug-summaries", handlerWrapper(handleBugSummaries)) http.Handle("/"+ns+"/subsystems", handlerWrapper(handleSubsystemsList)) diff --git a/dashboard/app/templates.html b/dashboard/app/templates.html index 4933c8bf4..4cfd5fb37 100644 --- a/dashboard/app/templates.html +++ b/dashboard/app/templates.html @@ -76,6 +76,8 @@ Use of this source code is governed by Apache 2 LICENSE that can be found in the {{end}} <a class="navigation_tab{{if eq .URLPath (printf "/%v/graph/bugs" $.Namespace)}}_selected{{end}}" href='/{{$.Namespace}}/graph/bugs'> <span style="color:DarkOrange;">📈</span> Kernel Health</a> + <a class="navigation_tab{{if eq .URLPath (printf "/%v/graph/found-bugs" $.Namespace)}}_selected{{end}}" href='/{{$.Namespace}}/graph/found-bugs'> + <span style="color:DarkOrange;">📈</span> Bugs/Month</a> <a class="navigation_tab{{if eq .URLPath (printf "/%v/graph/lifetimes" $.Namespace)}}_selected{{end}}" href='/{{$.Namespace}}/graph/lifetimes'> <span style="color:DarkOrange;">📈</span> Bug Lifetimes</a> <a class="navigation_tab{{if eq .URLPath (printf "/%v/graph/fuzzing" $.Namespace)}}_selected{{end}}" href='/{{$.Namespace}}/graph/fuzzing'> |
