Skip to content

Commit

Permalink
Merge branch 'master' into docs/translation_syscall_descriptions_syntax
Browse files Browse the repository at this point in the history
  • Loading branch information
QGrain committed Jul 16, 2024
2 parents c0adc83 + b66b37b commit 67a491b
Show file tree
Hide file tree
Showing 14 changed files with 846 additions and 340 deletions.
6 changes: 3 additions & 3 deletions dashboard/app/entities_spanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (

"cloud.google.com/go/civil"
"cloud.google.com/go/spanner"
"github.com/google/syzkaller/pkg/spanner/coveragedb"
"google.golang.org/api/iterator"
)

Expand All @@ -23,10 +24,9 @@ type CoverageHistory struct {
// MergedCoverage uses dates, not time.
func MergedCoverage(ctx context.Context, ns string, fromDate, toDate civil.Date) (*CoverageHistory, error) {
projectID := os.Getenv("GOOGLE_CLOUD_PROJECT")
database := "projects/" + projectID + "/instances/syzbot/databases/coverage"
client, err := spanner.NewClient(ctx, database)
client, err := coveragedb.NewClient(ctx, projectID)
if err != nil {
panic(fmt.Sprintf("spanner.NewClient() failed: %s", err.Error()))
return nil, fmt.Errorf("spanner.NewClient() failed: %s", err.Error())
}
defer client.Close()

Expand Down
3 changes: 2 additions & 1 deletion pkg/bisect/bisect.go
Original file line number Diff line number Diff line change
Expand Up @@ -830,6 +830,7 @@ func (env *env) bisectionDecision(total, bad, good, infra int) (vcs.BisectResult
// Boot errors, image test errors, skipped crashes.
skip := total - bad - good - infra

wantBadRuns := max(2, (total-infra)/6) // For 10 runs, require 2 crashes. For 20, require 3.
wantGoodRuns := total / 2
wantTotalRuns := total / 2
if env.flaky {
Expand All @@ -840,7 +841,7 @@ func (env *env) bisectionDecision(total, bad, good, infra int) (vcs.BisectResult
// We need a big enough number of good results, otherwise the chance of a false
// positive is too high.
return vcs.BisectGood, nil
} else if bad > 0 && (good+bad) >= wantTotalRuns {
} else if bad >= wantBadRuns && (good+bad) >= wantTotalRuns {
// We need enough (good+bad) results to conclude that the kernel revision itself
// is not too broken.
return vcs.BisectBad, nil
Expand Down
28 changes: 19 additions & 9 deletions pkg/bisect/bisect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,8 @@ func (env *testEnv) Test(numVMs int, reproSyz, reproOpts, reproC []byte) ([]inst
if (env.config == "baseline-repro" || env.config == "new-minimized-config" || env.config == "original config") &&
introduced && !fixed {
if env.test.flaky {
ret = crashErrors(1, numVMs-1, "crash occurs", env.test.reportType)
crashed := max(2, numVMs/6)
ret = crashErrors(crashed, numVMs-crashed, "crash occurs", env.test.reportType)
} else {
ret = crashErrors(numVMs, 0, "crash occurs", env.test.reportType)
}
Expand Down Expand Up @@ -352,10 +353,10 @@ var bisectionTests = []BisectionTest{
flaky: true,
introduced: "605",
extraTest: func(t *testing.T, res *Result) {
// False negative probability of each run is ~35%.
// We get three "good" results, so our accumulated confidence is ~27%.
assert.Less(t, res.Confidence, 0.3)
assert.Greater(t, res.Confidence, 0.2)
// False negative probability of each run is ~4%.
// We get three "good" results, so our accumulated confidence is ~85%.
assert.Less(t, res.Confidence, 0.9)
assert.Greater(t, res.Confidence, 0.8)
},
},
// Test bisection returns correct cause with different baseline/config combinations.
Expand Down Expand Up @@ -797,8 +798,8 @@ func TestBisectVerdict(t *testing.T) {
{
name: "many-total-and-infra",
total: 10,
good: 5,
bad: 1,
good: 4,
bad: 2,
infra: 2,
skip: 2,
verdict: vcs.BisectBad,
Expand Down Expand Up @@ -846,12 +847,21 @@ func TestBisectVerdict(t *testing.T) {
name: "flaky-many-skips",
flaky: true,
total: 20,
good: 9,
bad: 1,
good: 7,
bad: 3,
infra: 0,
skip: 10,
verdict: vcs.BisectBad,
},
{
name: "outlier-bad",
total: 10,
good: 9,
bad: 1,
infra: 0,
skip: 0,
verdict: vcs.BisectSkip,
},
}

for _, test := range tests {
Expand Down
191 changes: 191 additions & 0 deletions pkg/cover/heatmap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
// 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.

package cover

import (
"context"
_ "embed"
"fmt"
"html/template"
"io"
"sort"
"strings"

"cloud.google.com/go/civil"
"cloud.google.com/go/spanner"
"github.com/google/syzkaller/pkg/spanner/coveragedb"
"golang.org/x/exp/maps"
"google.golang.org/api/iterator"
)

type templateHeatmapRow struct {
Items []*templateHeatmapRow
Name string
Coverage []int64
IsDir bool
Depth int
LastDayInstrumented int64

builder map[string]*templateHeatmapRow
instrumented map[civil.Date]int64
covered map[civil.Date]int64
}

type templateHeatmap struct {
Root *templateHeatmapRow
Dates []string
}

func (thm *templateHeatmapRow) addParts(depth int, pathLeft []string, instrumented, covered int64, dateto civil.Date) {
thm.instrumented[dateto] += instrumented
thm.covered[dateto] += covered
if len(pathLeft) == 0 {
return
}
nextElement := pathLeft[0]
isDir := len(pathLeft) > 1
if _, ok := thm.builder[nextElement]; !ok {
thm.builder[nextElement] = &templateHeatmapRow{
Name: nextElement,
Depth: depth,
IsDir: isDir,
builder: make(map[string]*templateHeatmapRow),
instrumented: make(map[civil.Date]int64),
covered: make(map[civil.Date]int64),
}
}
thm.builder[nextElement].addParts(depth+1, pathLeft[1:], instrumented, covered, dateto)
}

func (thm *templateHeatmapRow) prepareDataFor(dates []civil.Date) {
thm.Items = maps.Values(thm.builder)
sort.Slice(thm.Items, func(i, j int) bool {
if thm.Items[i].IsDir != thm.Items[j].IsDir {
return thm.Items[i].IsDir
}
return thm.Items[i].Name < thm.Items[j].Name
})
for _, d := range dates {
var dateCoverage int64
if thm.instrumented[d] != 0 {
dateCoverage = 100 * thm.covered[d] / thm.instrumented[d]
}
thm.Coverage = append(thm.Coverage, dateCoverage)
}
if len(dates) > 0 {
lastDate := dates[len(dates)-1]
thm.LastDayInstrumented = thm.instrumented[lastDate]
}
for _, item := range thm.builder {
item.prepareDataFor(dates)
}
}

type fileCoverageAndDate struct {
Filepath string
Instrumented int64
Covered int64
Dateto civil.Date
}

func filesCoverageToTemplateData(fCov []*fileCoverageAndDate) *templateHeatmap {
res := templateHeatmap{
Root: &templateHeatmapRow{
builder: map[string]*templateHeatmapRow{},
instrumented: map[civil.Date]int64{},
covered: map[civil.Date]int64{},
},
}
dates := map[civil.Date]struct{}{}
for _, fc := range fCov {
res.Root.addParts(
0,
strings.Split(fc.Filepath, "/"),
fc.Instrumented,
fc.Covered,
fc.Dateto)
dates[fc.Dateto] = struct{}{}
}
sortedDates := maps.Keys(dates)
sort.Slice(sortedDates, func(i, j int) bool {
return sortedDates[i].Before(sortedDates[j])
})
for _, d := range sortedDates {
res.Dates = append(res.Dates, d.String())
}

res.Root.prepareDataFor(sortedDates)
return &res
}

func filesCoverageAndDates(ctx context.Context, projectID, ns string, fromDate, toDate civil.Date,
) ([]*fileCoverageAndDate, error) {
client, err := coveragedb.NewClient(ctx, projectID)
if err != nil {
return nil, fmt.Errorf("spanner.NewClient() failed: %s", err.Error())
}
defer client.Close()

stmt := spanner.Statement{
SQL: `
select
dateto,
instrumented,
covered,
filepath
from merge_history join files
on merge_history.session = files.session
where namespace=$1 and dateto>=$2 and dateto<=$3
`,
Params: map[string]interface{}{
"p1": ns,
"p2": fromDate,
"p3": toDate,
},
}

iter := client.Single().Query(ctx, stmt)
defer iter.Stop()
res := []*fileCoverageAndDate{}
for {
row, err := iter.Next()
if err == iterator.Done {
break
}
if err != nil {
return nil, fmt.Errorf("failed to iter.Next() spanner DB: %w", err)
}
var r fileCoverageAndDate
if err = row.ToStruct(&r); err != nil {
return nil, fmt.Errorf("failed to row.ToStruct() spanner DB: %w", err)
}
res = append(res, &r)
}
return res, nil
}

func DoHeatMap(w io.Writer, projectID, ns string, dateFrom, dateTo civil.Date) error {
covAndDates, err := filesCoverageAndDates(context.Background(), projectID, ns, dateFrom, dateTo)
if err != nil {
panic(err)
}
templateData := filesCoverageToTemplateData(covAndDates)
return heatmapTemplate.Execute(w, templateData)
}

func approximateInstrumented(points int64) string {
dim := "_"
if points > 10000 {
dim = "K"
points /= 1000
}
return fmt.Sprintf("%d%s", points, dim)
}

//go:embed templates/heatmap.html
var templatesHeatmap string
var templateHeatmapFuncs = template.FuncMap{
"approxInstr": approximateInstrumented,
}
var heatmapTemplate = template.Must(template.New("").Funcs(templateHeatmapFuncs).Parse(templatesHeatmap))
Loading

0 comments on commit 67a491b

Please sign in to comment.