aboutsummaryrefslogtreecommitdiffstats
path: root/dashboard
diff options
context:
space:
mode:
authorTaras Madan <tarasmadan@google.com>2025-01-09 11:47:01 +0100
committerTaras Madan <tarasmadan@google.com>2025-01-10 14:10:51 +0000
commitcc2730f070a7a06e09ddc1fcb7b609848a629ee2 (patch)
treedb4d7c2ec3f11e9a8b0f54ff1b701d591136afab /dashboard
parent67d7ec0a69193b2acbb8cdb5109d5505a9128ac5 (diff)
dashboard/app: move coverage handlers to separate file
Diffstat (limited to 'dashboard')
-rw-r--r--dashboard/app/coverage.go199
-rw-r--r--dashboard/app/graphs.go187
2 files changed, 199 insertions, 187 deletions
diff --git a/dashboard/app/coverage.go b/dashboard/app/coverage.go
new file mode 100644
index 000000000..e0ceab563
--- /dev/null
+++ b/dashboard/app/coverage.go
@@ -0,0 +1,199 @@
+// Copyright 2025 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.
+
+package main
+
+import (
+ "context"
+ "fmt"
+ "html/template"
+ "net/http"
+ "slices"
+ "strconv"
+
+ "cloud.google.com/go/civil"
+ "github.com/google/syzkaller/pkg/cover"
+ "github.com/google/syzkaller/pkg/coveragedb"
+ "github.com/google/syzkaller/pkg/covermerger"
+ "github.com/google/syzkaller/pkg/validator"
+)
+
+type funcStyleBodyJS func(ctx context.Context, projectID string, scope *cover.SelectScope, sss, managers []string,
+) (template.CSS, template.HTML, template.HTML, error)
+
+func handleCoverageHeatmap(c context.Context, w http.ResponseWriter, r *http.Request) error {
+ return handleHeatmap(c, w, r, cover.DoHeatMapStyleBodyJS)
+}
+
+func handleSubsystemsCoverageHeatmap(c context.Context, w http.ResponseWriter, r *http.Request) error {
+ return handleHeatmap(c, w, r, cover.DoSubsystemsHeatMapStyleBodyJS)
+}
+
+func handleHeatmap(c context.Context, w http.ResponseWriter, r *http.Request, f funcStyleBodyJS) error {
+ hdr, err := commonHeader(c, r, w, "")
+ if err != nil {
+ return err
+ }
+ ss := r.FormValue("subsystem")
+ manager := r.FormValue("manager")
+
+ periodType := r.FormValue("period")
+ if periodType == "" {
+ periodType = coveragedb.DayPeriod
+ }
+ if periodType != coveragedb.DayPeriod && periodType != coveragedb.MonthPeriod {
+ return fmt.Errorf("only day and month are allowed, but received %s instead, %w",
+ periodType, ErrClientBadRequest)
+ }
+
+ periodCount := r.FormValue("period_count")
+ if periodCount == "" {
+ periodCount = "4"
+ }
+ nPeriods, err := strconv.Atoi(periodCount)
+ if err != nil || nPeriods > 12 || nPeriods < 1 {
+ return fmt.Errorf("periods_count is wrong, expected [1, 12]: %w", err)
+ }
+
+ periods, err := coveragedb.GenNPeriodsTill(nPeriods, civil.DateOf(timeNow(c)), periodType)
+ if err != nil {
+ return fmt.Errorf("%s: %w", err.Error(), ErrClientBadRequest)
+ }
+ managers, err := CachedManagerList(c, hdr.Namespace)
+ if err != nil {
+ return err
+ }
+ ssService := getNsConfig(c, hdr.Namespace).Subsystems.Service
+ var subsystems []string
+ for _, ss := range ssService.List() {
+ subsystems = append(subsystems, ss.Name)
+ }
+ slices.Sort(managers)
+ slices.Sort(subsystems)
+
+ var style template.CSS
+ var body, js template.HTML
+ if style, body, js, err = f(c, "syzkaller",
+ &cover.SelectScope{
+ Ns: hdr.Namespace,
+ Subsystem: ss,
+ Manager: manager,
+ Periods: periods,
+ },
+ subsystems, managers); err != nil {
+ return fmt.Errorf("failed to generate heatmap: %w", err)
+ }
+ return serveTemplate(w, "custom_content.html", struct {
+ Header *uiHeader
+ *cover.StyleBodyJS
+ }{
+ Header: hdr,
+ StyleBodyJS: &cover.StyleBodyJS{
+ Style: style,
+ Body: body,
+ JS: js,
+ },
+ })
+}
+
+func makeProxyURIProvider(url string) covermerger.FuncProxyURI {
+ return func(filePath, commit string) string {
+ return fmt.Sprintf("%s/%s/%s", url, commit, filePath)
+ }
+}
+
+func handleFileCoverage(c context.Context, w http.ResponseWriter, r *http.Request) error {
+ hdr, err := commonHeader(c, r, w, "")
+ if err != nil {
+ return err
+ }
+ nsConfig := getNsConfig(c, hdr.Namespace)
+ if nsConfig.Coverage == nil || nsConfig.Coverage.WebGitURI == "" {
+ return ErrClientNotFound
+ }
+ dateToStr := r.FormValue("dateto")
+ periodType := r.FormValue("period")
+ targetCommit := r.FormValue("commit")
+ kernelFilePath := r.FormValue("filepath")
+ if err := validator.AnyError("input validation failed",
+ validator.TimePeriodType(periodType, "period"),
+ validator.CommitHash(targetCommit, "commit"),
+ validator.KernelFilePath(kernelFilePath, "filepath"),
+ ); err != nil {
+ return fmt.Errorf("%w: %w", err, ErrClientBadRequest)
+ }
+ targetDate, err := civil.ParseDate(dateToStr)
+ if err != nil {
+ return fmt.Errorf("civil.ParseDate(%s): %w", dateToStr, err)
+ }
+ tp, err := coveragedb.MakeTimePeriod(targetDate, periodType)
+ if err != nil {
+ return fmt.Errorf("coveragedb.MakeTimePeriod: %w", err)
+ }
+ mainNsRepo, _ := nsConfig.mainRepoBranch()
+ hitCounts, err := coveragedb.ReadLinesHitCount(c, hdr.Namespace, targetCommit, kernelFilePath, tp)
+ if err != nil {
+ return fmt.Errorf("coveragedb.ReadLinesHitCount: %w", err)
+ }
+
+ content, err := cover.RendFileCoverage(
+ mainNsRepo,
+ targetCommit,
+ kernelFilePath,
+ makeProxyURIProvider(nsConfig.Coverage.WebGitURI),
+ &covermerger.MergeResult{HitCounts: hitCounts},
+ cover.DefaultHTMLRenderConfig())
+ if err != nil {
+ return fmt.Errorf("cover.RendFileCoverage: %w", err)
+ }
+ w.Header().Set("Content-Type", "text/html")
+ w.Write([]byte(content))
+ return nil
+}
+
+func handleCoverageGraph(c context.Context, w http.ResponseWriter, r *http.Request) error {
+ hdr, err := commonHeader(c, r, w, "")
+ if err != nil {
+ return err
+ }
+ periodType := r.FormValue("period")
+ if periodType == "" {
+ periodType = coveragedb.QuarterPeriod
+ }
+ if periodType != coveragedb.QuarterPeriod && periodType != coveragedb.MonthPeriod {
+ return fmt.Errorf("only quarter and month are allowed, but received %s instead", periodType)
+ }
+ hist, err := MergedCoverage(c, hdr.Namespace, periodType)
+ if err != nil {
+ return err
+ }
+ periodEndDates, err := coveragedb.GenNPeriodsTill(12, civil.DateOf(timeNow(c)), periodType)
+ if err != nil {
+ return err
+ }
+ cols := []uiGraphColumn{}
+ for _, periodEndDate := range periodEndDates {
+ date := periodEndDate.DateTo.String()
+ if _, ok := hist.covered[date]; !ok || hist.instrumented[date] == 0 {
+ cols = append(cols, uiGraphColumn{Hint: date, Vals: []uiGraphValue{{IsNull: true}}})
+ } else {
+ val := float32(hist.covered[date]) / float32(hist.instrumented[date])
+ cols = append(cols, uiGraphColumn{
+ Hint: date,
+ Annotation: val,
+ Vals: []uiGraphValue{{Val: val}},
+ })
+ }
+ }
+ data := &uiHistogramPage{
+ Title: hdr.Namespace + " coverage",
+ Header: hdr,
+ Graph: &uiGraph{
+ Headers: []uiGraphHeader{
+ {Name: "Total", Color: "Red"},
+ },
+ Columns: cols,
+ },
+ }
+ return serveTemplate(w, "graph_histogram.html", data)
+}
diff --git a/dashboard/app/graphs.go b/dashboard/app/graphs.go
index 9918f9200..32ee9aaf2 100644
--- a/dashboard/app/graphs.go
+++ b/dashboard/app/graphs.go
@@ -6,20 +6,13 @@ package main
import (
"context"
"fmt"
- "html/template"
"net/http"
"net/url"
"regexp"
- "slices"
"sort"
"strconv"
"time"
- "cloud.google.com/go/civil"
- "github.com/google/syzkaller/pkg/cover"
- "github.com/google/syzkaller/pkg/coveragedb"
- "github.com/google/syzkaller/pkg/covermerger"
- "github.com/google/syzkaller/pkg/validator"
db "google.golang.org/appengine/v2/datastore"
)
@@ -195,186 +188,6 @@ func handleFoundBugsGraph(c context.Context, w http.ResponseWriter, r *http.Requ
return serveTemplate(w, "graph_histogram.html", data)
}
-type funcStyleBodyJS func(ctx context.Context, projectID string, scope *cover.SelectScope, sss, managers []string,
-) (template.CSS, template.HTML, template.HTML, error)
-
-func handleCoverageHeatmap(c context.Context, w http.ResponseWriter, r *http.Request) error {
- return handleHeatmap(c, w, r, cover.DoHeatMapStyleBodyJS)
-}
-
-func handleSubsystemsCoverageHeatmap(c context.Context, w http.ResponseWriter, r *http.Request) error {
- return handleHeatmap(c, w, r, cover.DoSubsystemsHeatMapStyleBodyJS)
-}
-
-func handleHeatmap(c context.Context, w http.ResponseWriter, r *http.Request, f funcStyleBodyJS) error {
- hdr, err := commonHeader(c, r, w, "")
- if err != nil {
- return err
- }
- ss := r.FormValue("subsystem")
- manager := r.FormValue("manager")
-
- periodType := r.FormValue("period")
- if periodType == "" {
- periodType = coveragedb.DayPeriod
- }
- if periodType != coveragedb.DayPeriod && periodType != coveragedb.MonthPeriod {
- return fmt.Errorf("only day and month are allowed, but received %s instead, %w",
- periodType, ErrClientBadRequest)
- }
-
- periodCount := r.FormValue("period_count")
- if periodCount == "" {
- periodCount = "4"
- }
- nPeriods, err := strconv.Atoi(periodCount)
- if err != nil || nPeriods > 12 || nPeriods < 1 {
- return fmt.Errorf("periods_count is wrong, expected [1, 12]: %w", err)
- }
-
- periods, err := coveragedb.GenNPeriodsTill(nPeriods, civil.DateOf(timeNow(c)), periodType)
- if err != nil {
- return fmt.Errorf("%s: %w", err.Error(), ErrClientBadRequest)
- }
- managers, err := CachedManagerList(c, hdr.Namespace)
- if err != nil {
- return err
- }
- ssService := getNsConfig(c, hdr.Namespace).Subsystems.Service
- var subsystems []string
- for _, ss := range ssService.List() {
- subsystems = append(subsystems, ss.Name)
- }
- slices.Sort(managers)
- slices.Sort(subsystems)
-
- var style template.CSS
- var body, js template.HTML
- if style, body, js, err = f(c, "syzkaller",
- &cover.SelectScope{
- Ns: hdr.Namespace,
- Subsystem: ss,
- Manager: manager,
- Periods: periods,
- },
- subsystems, managers); err != nil {
- return fmt.Errorf("failed to generate heatmap: %w", err)
- }
- return serveTemplate(w, "custom_content.html", struct {
- Header *uiHeader
- *cover.StyleBodyJS
- }{
- Header: hdr,
- StyleBodyJS: &cover.StyleBodyJS{
- Style: style,
- Body: body,
- JS: js,
- },
- })
-}
-
-func makeProxyURIProvider(url string) covermerger.FuncProxyURI {
- return func(filePath, commit string) string {
- return fmt.Sprintf("%s/%s/%s", url, commit, filePath)
- }
-}
-
-func handleFileCoverage(c context.Context, w http.ResponseWriter, r *http.Request) error {
- hdr, err := commonHeader(c, r, w, "")
- if err != nil {
- return err
- }
- nsConfig := getNsConfig(c, hdr.Namespace)
- if nsConfig.Coverage == nil || nsConfig.Coverage.WebGitURI == "" {
- return ErrClientNotFound
- }
- dateToStr := r.FormValue("dateto")
- periodType := r.FormValue("period")
- targetCommit := r.FormValue("commit")
- kernelFilePath := r.FormValue("filepath")
- if err := validator.AnyError("input validation failed",
- validator.TimePeriodType(periodType, "period"),
- validator.CommitHash(targetCommit, "commit"),
- validator.KernelFilePath(kernelFilePath, "filepath"),
- ); err != nil {
- return fmt.Errorf("%w: %w", err, ErrClientBadRequest)
- }
- targetDate, err := civil.ParseDate(dateToStr)
- if err != nil {
- return fmt.Errorf("civil.ParseDate(%s): %w", dateToStr, err)
- }
- tp, err := coveragedb.MakeTimePeriod(targetDate, periodType)
- if err != nil {
- return fmt.Errorf("coveragedb.MakeTimePeriod: %w", err)
- }
- mainNsRepo, _ := nsConfig.mainRepoBranch()
- hitCounts, err := coveragedb.ReadLinesHitCount(c, hdr.Namespace, targetCommit, kernelFilePath, tp)
- if err != nil {
- return fmt.Errorf("coveragedb.ReadLinesHitCount: %w", err)
- }
-
- content, err := cover.RendFileCoverage(
- mainNsRepo,
- targetCommit,
- kernelFilePath,
- makeProxyURIProvider(nsConfig.Coverage.WebGitURI),
- &covermerger.MergeResult{HitCounts: hitCounts},
- cover.DefaultHTMLRenderConfig())
- if err != nil {
- return fmt.Errorf("cover.RendFileCoverage: %w", err)
- }
- w.Header().Set("Content-Type", "text/html")
- w.Write([]byte(content))
- return nil
-}
-
-func handleCoverageGraph(c context.Context, w http.ResponseWriter, r *http.Request) error {
- hdr, err := commonHeader(c, r, w, "")
- if err != nil {
- return err
- }
- periodType := r.FormValue("period")
- if periodType == "" {
- periodType = coveragedb.QuarterPeriod
- }
- if periodType != coveragedb.QuarterPeriod && periodType != coveragedb.MonthPeriod {
- return fmt.Errorf("only quarter and month are allowed, but received %s instead", periodType)
- }
- hist, err := MergedCoverage(c, hdr.Namespace, periodType)
- if err != nil {
- return err
- }
- periodEndDates, err := coveragedb.GenNPeriodsTill(12, civil.DateOf(timeNow(c)), periodType)
- if err != nil {
- return err
- }
- cols := []uiGraphColumn{}
- for _, periodEndDate := range periodEndDates {
- date := periodEndDate.DateTo.String()
- if _, ok := hist.covered[date]; !ok || hist.instrumented[date] == 0 {
- cols = append(cols, uiGraphColumn{Hint: date, Vals: []uiGraphValue{{IsNull: true}}})
- } else {
- val := float32(hist.covered[date]) / float32(hist.instrumented[date])
- cols = append(cols, uiGraphColumn{
- Hint: date,
- Annotation: val,
- Vals: []uiGraphValue{{Val: val}},
- })
- }
- }
- data := &uiHistogramPage{
- Title: hdr.Namespace + " coverage",
- Header: hdr,
- Graph: &uiGraph{
- Headers: []uiGraphHeader{
- {Name: "Total", Color: "Red"},
- },
- Columns: cols,
- },
- }
- return serveTemplate(w, "graph_histogram.html", data)
-}
-
func loadGraphBugs(c context.Context, ns string) ([]*Bug, error) {
filter := func(query *db.Query) *db.Query {
return query.Filter("Namespace=", ns)