aboutsummaryrefslogtreecommitdiffstats
path: root/syz-cluster/pkg
diff options
context:
space:
mode:
authorAleksandr Nogikh <nogikh@google.com>2025-08-13 17:32:02 +0200
committerAleksandr Nogikh <nogikh@google.com>2025-08-14 11:29:47 +0000
commit83b914a1837f0f4de012f4dc6bdc84c9d5bab713 (patch)
tree4014acd61a94833bd66b17d8066979d2dee33807 /syz-cluster/pkg
parent028e3578dbbde98fdb5a4d170f0e1dce7a8a2ab4 (diff)
syz-cluster: display statistics
Add a web dashboard page with the main statistics concerning patch series fuzzing. Improve the navigation bar on top of the page.
Diffstat (limited to 'syz-cluster/pkg')
-rw-r--r--syz-cluster/pkg/db/stats_repo.go92
-rw-r--r--syz-cluster/pkg/db/stats_repo_test.go33
2 files changed, 125 insertions, 0 deletions
diff --git a/syz-cluster/pkg/db/stats_repo.go b/syz-cluster/pkg/db/stats_repo.go
new file mode 100644
index 000000000..fa6c1d901
--- /dev/null
+++ b/syz-cluster/pkg/db/stats_repo.go
@@ -0,0 +1,92 @@
+// 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 db
+
+import (
+ "context"
+ "time"
+
+ "cloud.google.com/go/spanner"
+)
+
+type StatsRepository struct {
+ client *spanner.Client
+}
+
+func NewStatsRepository(client *spanner.Client) *StatsRepository {
+ return &StatsRepository{
+ client: client,
+ }
+}
+
+type CountPerWeek struct {
+ Date time.Time `spanner:"Date"`
+ Count int64 `spanner:"Count"`
+}
+
+func (repo *StatsRepository) ProcessedSeriesPerWeek(ctx context.Context) (
+ []*CountPerWeek, error) {
+ return readEntities[CountPerWeek](ctx, repo.client.Single(), spanner.Statement{
+ SQL: `SELECT
+ TIMESTAMP_TRUNC(Sessions.FinishedAt, WEEK) as Date,
+ COUNT(*) as Count
+FROM Series
+JOIN Sessions ON Sessions.ID = Series.LatestSessionID
+WHERE FinishedAt IS NOT NULL
+GROUP BY Date
+ORDER BY Date`,
+ })
+}
+
+func (repo *StatsRepository) FindingsPerWeek(ctx context.Context) (
+ []*CountPerWeek, error) {
+ return readEntities[CountPerWeek](ctx, repo.client.Single(), spanner.Statement{
+ SQL: `SELECT
+ TIMESTAMP_TRUNC(Sessions.FinishedAt, WEEK) as Date,
+ COUNT(*) as Count
+FROM Findings
+JOIN Sessions ON Sessions.ID = Findings.SessionID
+GROUP BY Date
+ORDER BY Date`,
+ })
+}
+
+type StatusPerWeek struct {
+ Date time.Time `spanner:"Date"`
+ Finished int64 `spanner:"Finished"`
+ Skipped int64 `spanner:"Skipped"`
+}
+
+func (repo *StatsRepository) SessionStatusPerWeek(ctx context.Context) (
+ []*StatusPerWeek, error) {
+ return readEntities[StatusPerWeek](ctx, repo.client.Single(), spanner.Statement{
+ SQL: `SELECT
+ TIMESTAMP_TRUNC(Sessions.FinishedAt, WEEK) as Date,
+ COUNTIF(Sessions.SkipReason IS NULL) as Finished,
+ COUNTIF(Sessions.SkipReason IS NOT NULL) as Skipped
+FROM Series
+JOIN Sessions ON Sessions.ID = Series.LatestSessionID
+WHERE FinishedAt IS NOT NULL
+GROUP BY Date
+ORDER BY Date`,
+ })
+}
+
+type DelayPerWeek struct {
+ Date time.Time `spanner:"Date"`
+ DelayHours float64 `spanner:"AvgDelayHours"`
+}
+
+func (repo *StatsRepository) DelayPerWeek(ctx context.Context) (
+ []*DelayPerWeek, error) {
+ return readEntities[DelayPerWeek](ctx, repo.client.Single(), spanner.Statement{
+ SQL: `SELECT
+ TIMESTAMP_TRUNC(Sessions.StartedAt, WEEK) as Date,
+ AVG(TIMESTAMP_DIFF(Sessions.StartedAt,Sessions.CreatedAt, HOUR)) as AvgDelayHours
+FROM Sessions
+WHERE StartedAt IS NOT NULL
+GROUP BY Date
+ORDER BY Date`,
+ })
+}
diff --git a/syz-cluster/pkg/db/stats_repo_test.go b/syz-cluster/pkg/db/stats_repo_test.go
new file mode 100644
index 000000000..637894edf
--- /dev/null
+++ b/syz-cluster/pkg/db/stats_repo_test.go
@@ -0,0 +1,33 @@
+// 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 db
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestStatsSQLs(t *testing.T) {
+ // Ideally, there should be some proper tests, but for now let's at least
+ // check that the SQL queries themselves have no errors.
+ // That already brings a lot of value.
+ client, ctx := NewTransientDB(t)
+
+ // Add some data to test field decoding as well.
+ dtd := &dummyTestData{t, ctx, client}
+ session := dtd.dummySession(dtd.dummySeries())
+ dtd.startSession(session)
+ dtd.finishSession(session)
+
+ statsRepo := NewStatsRepository(client)
+ _, err := statsRepo.ProcessedSeriesPerWeek(ctx)
+ assert.NoError(t, err)
+ _, err = statsRepo.FindingsPerWeek(ctx)
+ assert.NoError(t, err)
+ _, err = statsRepo.SessionStatusPerWeek(ctx)
+ assert.NoError(t, err)
+ _, err = statsRepo.DelayPerWeek(ctx)
+ assert.NoError(t, err)
+}