aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTaras Madan <tarasmadan@google.com>2024-08-30 10:28:50 +0200
committerTaras Madan <tarasmadan@google.com>2024-08-30 11:51:11 +0000
commitf885a8ff9168f5e83a85ec9b1b02764e02c0f4e8 (patch)
tree98a8ba6bc4c9029ba934e6d8e1a034e4ad1debc5
parent3cb12728f957eec0a79ed1d53e0719fb1a772a6b (diff)
dashboard/app: total coverage is about months or quarters
Total coverage page defaults to quarters (?period=quarter). Monthly data is available with ?period=month.
-rw-r--r--dashboard/app/entities_spanner.go31
-rw-r--r--dashboard/app/graphs.go23
-rw-r--r--pkg/coveragedb/time_period.go52
-rw-r--r--pkg/coveragedb/time_period_test.go28
4 files changed, 109 insertions, 25 deletions
diff --git a/dashboard/app/entities_spanner.go b/dashboard/app/entities_spanner.go
index abe7556d3..ff154986e 100644
--- a/dashboard/app/entities_spanner.go
+++ b/dashboard/app/entities_spanner.go
@@ -19,10 +19,11 @@ import (
type CoverageHistory struct {
instrumented map[string]int64
covered map[string]int64
+ periods map[coveragedb.TimePeriod]struct{}
}
// MergedCoverage uses dates, not time.
-func MergedCoverage(ctx context.Context, ns string, fromDate, toDate civil.Date) (*CoverageHistory, error) {
+func MergedCoverage(ctx context.Context, ns, periodType string) (*CoverageHistory, error) {
projectID := os.Getenv("GOOGLE_CLOUD_PROJECT")
client, err := coveragedb.NewClient(ctx, projectID)
if err != nil {
@@ -30,20 +31,29 @@ func MergedCoverage(ctx context.Context, ns string, fromDate, toDate civil.Date)
}
defer client.Close()
+ minDays, maxDays, err := coveragedb.MinMaxDays(periodType)
+ if err != nil {
+ return nil, fmt.Errorf("coveragedb.MinMaxDays: %w", err)
+ }
+ pOps, err := coveragedb.PeriodOps(periodType)
+ if err != nil {
+ return nil, fmt.Errorf("coveragedb.PeriodOps: %w", err)
+ }
stmt := spanner.Statement{
SQL: `
select
dateto as targetdate,
+ duration as days,
cast(sum(instrumented) as INTEGER) as instrumented,
cast(sum(covered) as INTEGER) as covered
from merge_history join files
on merge_history.session = files.session
-where namespace=$1 and dateto>=$2 and dateto<=$3
-group by dateto`,
+where namespace=$1 and duration>=$2 and duration<=$3
+group by dateto, duration`,
Params: map[string]interface{}{
"p1": ns,
- "p2": fromDate,
- "p3": toDate,
+ "p2": minDays,
+ "p3": maxDays,
},
}
@@ -52,6 +62,7 @@ group by dateto`,
res := &CoverageHistory{
instrumented: map[string]int64{},
covered: map[string]int64{},
+ periods: map[coveragedb.TimePeriod]struct{}{},
}
for {
row, err := iter.Next()
@@ -63,14 +74,24 @@ group by dateto`,
}
var r struct {
Targetdate civil.Date
+ Days int64
Instrumented int64
Covered int64
}
if err = row.ToStruct(&r); err != nil {
return nil, fmt.Errorf("failed to row.ToStruct() spanner DB: %w", err)
}
+ period := coveragedb.TimePeriod{DateTo: r.Targetdate, Days: int(r.Days)}
+ if !pOps.IsValidPeriod(period) {
+ continue
+ }
res.instrumented[r.Targetdate.String()] = r.Instrumented
res.covered[r.Targetdate.String()] = r.Covered
+ if _, found := res.periods[period]; found {
+ return nil, fmt.Errorf("db error: only one period expected for date %s, days %d",
+ period.DateTo.String(), period.Days)
+ }
+ res.periods[period] = struct{}{}
}
return res, nil
}
diff --git a/dashboard/app/graphs.go b/dashboard/app/graphs.go
index 7e9a45c84..e4ded73d6 100644
--- a/dashboard/app/graphs.go
+++ b/dashboard/app/graphs.go
@@ -16,6 +16,7 @@ import (
"cloud.google.com/go/civil"
"github.com/google/syzkaller/pkg/cover"
+ "github.com/google/syzkaller/pkg/coveragedb"
db "google.golang.org/appengine/v2/datastore"
)
@@ -232,18 +233,26 @@ func handleCoverageGraph(c context.Context, w http.ResponseWriter, r *http.Reque
if err != nil {
return err
}
- yesterday := civil.DateOf(time.Now().Add(-1 * 24 * time.Hour))
- monthAgo := yesterday.AddDays(-31)
- hist, err := MergedCoverage(c, hdr.Namespace, monthAgo, yesterday)
+ 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
}
- dates := []string{}
- for i := 31; i >= 0; i-- {
- dates = append(dates, yesterday.AddDays(-i).String())
+ pOps, err := coveragedb.PeriodOps(periodType)
+ if err != nil {
+ return err
}
+ periodEndDates := coveragedb.GenNPeriodEndDatesTill(12, civil.DateOf(time.Now()), pOps)
+
cols := []uiGraphColumn{}
- for _, date := range dates {
+ for _, periodEndDate := range periodEndDates {
+ date := periodEndDate.String()
if _, ok := hist.covered[date]; !ok || hist.instrumented[date] == 0 {
cols = append(cols, uiGraphColumn{Hint: date, Vals: []uiGraphValue{{IsNull: true}}})
} else {
diff --git a/pkg/coveragedb/time_period.go b/pkg/coveragedb/time_period.go
index 463ee6c41..b3a2aad73 100644
--- a/pkg/coveragedb/time_period.go
+++ b/pkg/coveragedb/time_period.go
@@ -4,6 +4,8 @@
package coveragedb
import (
+ "errors"
+ "slices"
"sort"
"cloud.google.com/go/civil"
@@ -14,19 +16,59 @@ type TimePeriod struct {
Days int
}
+const (
+ MonthPeriod = "month"
+ QuarterPeriod = "quarter"
+)
+
+var errUnknownTimePeriodType = errors.New("unknown time period type")
+
+func MinMaxDays(periodType string) (int, int, error) {
+ switch periodType {
+ case MonthPeriod:
+ return 28, 31, nil
+ case QuarterPeriod:
+ return 31 + 28 + 31, 31 + 30 + 31, nil
+ default:
+ return 0, 0, errUnknownTimePeriodType
+ }
+}
+
+func PeriodOps(periodType string) (periodOps, error) {
+ switch periodType {
+ case MonthPeriod:
+ return &MonthPeriodOps{}, nil
+ case QuarterPeriod:
+ return &QuarterPeriodOps{}, nil
+ default:
+ return nil, errUnknownTimePeriodType
+ }
+}
+
type periodOps interface {
- isValidPeriod(p TimePeriod) bool
+ IsValidPeriod(p TimePeriod) bool
lastPeriodDate(d civil.Date) civil.Date
pointedPeriodDays(d civil.Date) int
}
+func GenNPeriodEndDatesTill(n int, d civil.Date, po periodOps) []civil.Date {
+ var res []civil.Date
+ for i := 0; i < n; i++ {
+ d = po.lastPeriodDate(d)
+ res = append(res, d)
+ d = d.AddDays(-po.pointedPeriodDays(d))
+ }
+ slices.Reverse(res)
+ return res
+}
+
type DayPeriodOps struct{}
func (dpo *DayPeriodOps) lastPeriodDate(d civil.Date) civil.Date {
return d
}
-func (dpo *DayPeriodOps) isValidPeriod(p TimePeriod) bool {
+func (dpo *DayPeriodOps) IsValidPeriod(p TimePeriod) bool {
return p.Days == 1
}
@@ -43,7 +85,7 @@ func (m *MonthPeriodOps) lastPeriodDate(d civil.Date) civil.Date {
return d.AddDays(-1)
}
-func (m *MonthPeriodOps) isValidPeriod(p TimePeriod) bool {
+func (m *MonthPeriodOps) IsValidPeriod(p TimePeriod) bool {
lmd := m.lastPeriodDate(p.DateTo)
return lmd == p.DateTo && p.Days == lmd.Day
}
@@ -54,7 +96,7 @@ func (m *MonthPeriodOps) pointedPeriodDays(d civil.Date) int {
type QuarterPeriodOps struct{}
-func (q *QuarterPeriodOps) isValidPeriod(p TimePeriod) bool {
+func (q *QuarterPeriodOps) IsValidPeriod(p TimePeriod) bool {
lmd := q.lastPeriodDate(p.DateTo)
return lmd == p.DateTo && p.Days == q.pointedPeriodDays(lmd)
}
@@ -83,7 +125,7 @@ func PeriodsToMerge(srcDates, mergedPeriods []TimePeriod, srcRows, mergedRows []
periodRows[periodID] += srcRows[i]
}
for i, period := range mergedPeriods {
- if !ops.isValidPeriod(period) {
+ if !ops.IsValidPeriod(period) {
continue
}
mergerPeriodID := period.DateTo
diff --git a/pkg/coveragedb/time_period_test.go b/pkg/coveragedb/time_period_test.go
index 85fb4b407..86201e4db 100644
--- a/pkg/coveragedb/time_period_test.go
+++ b/pkg/coveragedb/time_period_test.go
@@ -19,10 +19,14 @@ func TestDayPeriodOps(t *testing.T) {
assert.Equal(t, "2024-02-20", ops.lastPeriodDate(d).String())
- assert.True(t, ops.isValidPeriod(goodPeriod))
- assert.False(t, ops.isValidPeriod(badPeriod))
+ assert.True(t, ops.IsValidPeriod(goodPeriod))
+ assert.False(t, ops.IsValidPeriod(badPeriod))
assert.Equal(t, 1, ops.pointedPeriodDays(d))
+
+ assert.Equal(t,
+ []civil.Date{{Year: 2024, Month: time.February, Day: 19}, {Year: 2024, Month: time.February, Day: 20}},
+ GenNPeriodEndDatesTill(2, d, ops))
}
func TestMonthPeriodOps(t *testing.T) {
@@ -37,11 +41,15 @@ func TestMonthPeriodOps(t *testing.T) {
assert.Equal(t, "2024-02-29", ops.lastPeriodDate(midMonthDate).String())
- assert.True(t, ops.isValidPeriod(goodPeriod))
- assert.False(t, ops.isValidPeriod(badPeriod1))
- assert.False(t, ops.isValidPeriod(badPeriod2))
+ assert.True(t, ops.IsValidPeriod(goodPeriod))
+ assert.False(t, ops.IsValidPeriod(badPeriod1))
+ assert.False(t, ops.IsValidPeriod(badPeriod2))
assert.Equal(t, 29, ops.pointedPeriodDays(midMonthDate))
+
+ assert.Equal(t,
+ []civil.Date{{Year: 2024, Month: time.January, Day: 31}, {Year: 2024, Month: time.February, Day: 29}},
+ GenNPeriodEndDatesTill(2, goodPeriod.DateTo, ops))
}
func TestQuarterPeriodOps(t *testing.T) {
@@ -57,11 +65,15 @@ func TestQuarterPeriodOps(t *testing.T) {
assert.Equal(t, "2024-03-31", ops.lastPeriodDate(midQuarterDate).String())
- assert.True(t, ops.isValidPeriod(goodPeriod))
- assert.False(t, ops.isValidPeriod(badPeriod1))
- assert.False(t, ops.isValidPeriod(badPeriod2))
+ assert.True(t, ops.IsValidPeriod(goodPeriod))
+ assert.False(t, ops.IsValidPeriod(badPeriod1))
+ assert.False(t, ops.IsValidPeriod(badPeriod2))
assert.Equal(t, 31+29+31, ops.pointedPeriodDays(midQuarterDate))
+
+ assert.Equal(t,
+ []civil.Date{{Year: 2023, Month: time.December, Day: 31}, {Year: 2024, Month: time.March, Day: 31}},
+ GenNPeriodEndDatesTill(2, goodPeriod.DateTo, ops))
}
func TestPeriodsToMerge(t *testing.T) {