diff options
| author | Taras Madan <tarasmadan@google.com> | 2025-01-09 11:47:01 +0100 |
|---|---|---|
| committer | Taras Madan <tarasmadan@google.com> | 2025-01-10 14:10:51 +0000 |
| commit | cc2730f070a7a06e09ddc1fcb7b609848a629ee2 (patch) | |
| tree | db4d7c2ec3f11e9a8b0f54ff1b701d591136afab /dashboard | |
| parent | 67d7ec0a69193b2acbb8cdb5109d5505a9128ac5 (diff) | |
dashboard/app: move coverage handlers to separate file
Diffstat (limited to 'dashboard')
| -rw-r--r-- | dashboard/app/coverage.go | 199 | ||||
| -rw-r--r-- | dashboard/app/graphs.go | 187 |
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) |
