Skip to content

Commit be8490e

Browse files
committed
dashboard/app: AI patching jobs
1 parent d246422 commit be8490e

File tree

15 files changed

+370
-3
lines changed

15 files changed

+370
-3
lines changed

dashboard/app/ai.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Copyright 2025 syzkaller project authors. All rights reserved.
2+
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
3+
4+
package main
5+
6+
import (
7+
"context"
8+
"fmt"
9+
"net/http"
10+
//"slices"
11+
12+
//"cloud.google.com/go/spanner"
13+
"github.com/google/syzkaller/dashboard/app/aidb"
14+
"github.com/google/syzkaller/dashboard/dashapi"
15+
//"google.golang.org/api/iterator"
16+
//"google.golang.org/appengine/v2"
17+
"google.golang.org/appengine/v2/log"
18+
)
19+
20+
type uiAIPage struct {
21+
Header *uiHeader
22+
Workflows []*aidb.Workflow
23+
}
24+
25+
func handleAIPage(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
26+
hdr, err := commonHeader(ctx, r, w, "")
27+
if err != nil {
28+
return err
29+
}
30+
workflows, err := aidb.LoadWorkflows(ctx)
31+
if err != nil {
32+
return err
33+
}
34+
page := &uiAIPage{
35+
Header: hdr,
36+
Workflows: workflows,
37+
}
38+
return serveTemplate(w, "ai.html", page)
39+
}
40+
41+
func apiAIJobPoll(ctx context.Context, req *dashapi.AIJobPollReq) (any, error) {
42+
if err := aidb.UpdateWorkflows(ctx, req.Workflows); err != nil {
43+
log.Errorf(ctx, "storeAIWorkflows: %v", err)
44+
}
45+
resp := &dashapi.AIJobPollResp{}
46+
return resp, nil
47+
}
48+
49+
func apiAIJobDone(ctx context.Context, req *dashapi.AIJobDoneReq) (any, error) {
50+
return nil, nil
51+
}
52+
53+
func apiAIJournal(ctx context.Context, req *dashapi.AIJournalReq) (any, error) {
54+
return nil, nil
55+
}
56+
57+
func aiWorkflowCreate(ctx context.Context, bug *Bug) error {
58+
crash, _, err := findCrashForBug(ctx, bug)
59+
if err != nil {
60+
return err
61+
}
62+
if crash.ReproSyz == 0 {
63+
return fmt.Errorf("the bug does not have a reproducer")
64+
}
65+
return nil
66+
}

dashboard/app/aidb/entities.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Copyright 2025 syzkaller project authors. All rights reserved.
2+
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
3+
4+
package aidb
5+
6+
type Workflow struct {
7+
Name string
8+
Active bool
9+
}

dashboard/app/aidb/helpers.go

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// Copyright 2025 syzkaller project authors. All rights reserved.
2+
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
3+
4+
package aidb
5+
6+
import (
7+
"context"
8+
9+
"cloud.google.com/go/spanner"
10+
//"google.golang.org/api/iterator"
11+
"google.golang.org/appengine/v2"
12+
)
13+
14+
func LoadWorkflows(ctx context.Context) ([]*Workflow, error) {
15+
client, err := dbClient(ctx)
16+
if err != nil {
17+
return nil, err
18+
}
19+
defer client.Close()
20+
iter := client.Single().Query(ctx, spanner.Statement{
21+
SQL: `select * from workflows`,
22+
})
23+
defer iter.Stop()
24+
var workflows []*Workflow
25+
err = spanner.SelectAll(iter, &workflows)
26+
return workflows, err
27+
}
28+
29+
func LoadActiveWorkflows(ctx context.Context) ([]string, error) {
30+
workflows, err := LoadWorkflows(ctx)
31+
if err != nil {
32+
return nil, err
33+
}
34+
var active []string
35+
for _, flow := range workflows {
36+
if flow.Active {
37+
active = append(active, flow.Name)
38+
}
39+
}
40+
return active, nil
41+
}
42+
43+
func UpdateWorkflows(ctx context.Context, active []string) error {
44+
workflows, err := LoadWorkflows(ctx)
45+
if err != nil {
46+
return err
47+
}
48+
m := make(map[string]bool)
49+
for _, flow := range active {
50+
m[flow] = true
51+
}
52+
update := false
53+
for _, flow := range workflows {
54+
active := m[flow.Name]
55+
delete(m, flow.Name)
56+
if flow.Active != active {
57+
update = true
58+
flow.Active = active
59+
}
60+
}
61+
for name := range m {
62+
update = true
63+
workflows = append(workflows, &Workflow{
64+
Name: name,
65+
Active: true,
66+
})
67+
}
68+
if !update {
69+
return nil
70+
}
71+
client, err := dbClient(ctx)
72+
if err != nil {
73+
return err
74+
}
75+
defer client.Close()
76+
var mutations []*spanner.Mutation
77+
for _, flow := range workflows {
78+
mut, err := spanner.InsertOrUpdateStruct("workflows", flow)
79+
if err != nil {
80+
return err
81+
}
82+
mutations = append(mutations, mut)
83+
}
84+
_, err = client.Apply(ctx, mutations)
85+
return err
86+
}
87+
88+
func dbClient(ctx context.Context) (*spanner.Client, error) {
89+
database := "projects/" + appengine.AppID(ctx) + "/instances/syzbot/databases/ai"
90+
return spanner.NewClient(ctx, database)
91+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
DROP TABLE workflows;
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
CREATE TABLE Workflows (
2+
Name STRING(1000) NOT NULL,
3+
Active BOOL NOT NULL,
4+
) PRIMARY KEY (Name);
5+
6+
CREATE TABLE Jobs (
7+
ID STRING(1000) NOT NULL,
8+
Workflow STRING(1000) NOT NULL,
9+
-- Status STRING(128) NOT NULL,
10+
Error STRING(1000),
11+
Created TIMESTAMP NOT NULL,
12+
Started TIMESTAMP,
13+
Finished TIMESTAMP,
14+
-- Patching STRUCT<>,
15+
16+
-- CONSTRAINT StatusEnum CHECK (Status IN ('pending', 'running', 'finished')),
17+
CONSTRAINT FK_JobWorkflowJob FOREIGN KEY (Workflow) REFERENCES Workflows (Name),
18+
) PRIMARY KEY (ID);
19+
20+
CREATE TABLE PatchingJobs (
21+
ID STRING(1000) NOT NULL,
22+
ReproOpts INT64 NOT NULL,
23+
ReproSyz INT64 NOT NULL,
24+
ReproC INT64,
25+
KernelConfig INT64 NOT NULL,
26+
SyzkallerCommit STRING(100) NOT NULL,
27+
28+
CONSTRAINT FK_PatchingJobJob FOREIGN KEY (ID) REFERENCES Jobs (ID),
29+
) PRIMARY KEY (ID);
30+
31+
CREATE TABLE Events (
32+
JobID STRING(1000) NOT NULL,
33+
Seq INT64,
34+
-- XXXXXXXXXXXXXXXXXXXX
35+
36+
CONSTRAINT FK_EventJob FOREIGN KEY (JobID) REFERENCES Jobs (ID),
37+
) PRIMARY KEY (JobID, Seq);
38+

dashboard/app/api.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,9 @@ var apiHandlers = map[string]APIHandler{
6060
"save_discussion": typedHandler(apiSaveDiscussion),
6161
"create_upload_url": typedHandler(apiCreateUploadURL),
6262
"send_email": typedHandler(apiSendEmail),
63+
"ai_job_poll": typedHandler(apiAIJobPoll),
64+
"ai_job_done": typedHandler(apiAIJobDone),
65+
"ai_journal": typedHandler(apiAIJournal),
6366
"save_coverage": gcsPayloadHandler(apiSaveCoverage),
6467
"upload_build": nsHandler(apiUploadBuild),
6568
"builder_poll": nsHandler(apiBuilderPoll),

dashboard/app/app.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ handlers:
2525
- url: /static
2626
static_dir: static
2727
secure: always
28-
- url: /(admin|cron/.*)
28+
- url: /(admin|debug|ai|cron/.*)
2929
script: auto
3030
login: admin
3131
secure: always

dashboard/app/config.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,8 @@ type Config struct {
8787
// If set, this namespace is not actively tested, no notifications are sent, etc.
8888
// It's kept mostly read-only for historical reference.
8989
Decommissioned bool
90+
// Enable AI workflows for the namespace.
91+
AI bool
9092
// Name used in UI.
9193
DisplayTitle string
9294
// Unique string that allows to show "similar bugs" across different namespaces.

dashboard/app/handler.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,7 @@ func serveTemplate(w http.ResponseWriter, name string, data any) error {
214214

215215
type uiHeader struct {
216216
Admin bool
217+
AI bool
217218
URLPath string
218219
LoginLink string
219220
AnalyticsTrackingID string
@@ -225,6 +226,7 @@ type uiHeader struct {
225226
Namespaces []uiNamespace
226227
ShowSubsystems bool
227228
ShowCoverageMenu bool
229+
Message string // Show message box with this message when the page loads.
228230
}
229231

230232
type uiNamespace struct {
@@ -299,8 +301,10 @@ func commonHeader(c context.Context, r *http.Request, w http.ResponseWriter, ns
299301
}
300302
}
301303
if ns != adminPage {
304+
cfg := getNsConfig(c, ns)
302305
h.Namespace = ns
303-
h.ShowSubsystems = getNsConfig(c, ns).Subsystems.Service != nil
306+
h.AI = h.Admin && cfg.AI
307+
h.ShowSubsystems = cfg.Subsystems.Service != nil
304308
cookie.Namespace = ns
305309
encodeCookie(w, cookie)
306310
cached, err := CacheGet(c, r, ns)
@@ -309,7 +313,7 @@ func commonHeader(c context.Context, r *http.Request, w http.ResponseWriter, ns
309313
}
310314
h.BugCounts = &cached.Total
311315
h.MissingBackports = cached.MissingBackports
312-
h.ShowCoverageMenu = getNsConfig(c, ns).Coverage != nil
316+
h.ShowCoverageMenu = cfg.Coverage != nil
313317
}
314318
return h, nil
315319
}

dashboard/app/init_ai_db.sh

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#!/usr/bin/env bash
2+
# Copyright 2025 syzkaller project authors. All rights reserved.
3+
# Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
4+
5+
set -eux
6+
7+
gcloud spanner databases ddl update ai --instance=syzbot --project=syzkaller --ddl="
8+
DROP TABLE IF EXISTS workflows;
9+
10+
CREATE TABLE
11+
workflows (
12+
pk int64,
13+
"manager" text,
14+
"filepath" text,
15+
"instrumented" bigint,
16+
"covered" bigint,
17+
"linesinstrumented" bigint[],
18+
"hitcounts" bigint[],
19+
PRIMARY KEY
20+
(session, manager, filepath) );')
21+
"
22+
23+
echo "create table 'files'"
24+
create_table=$( echo -n '
25+
CREATE TABLE
26+
files (
27+
"session" text,
28+
"manager" text,
29+
"filepath" text,
30+
"instrumented" bigint,
31+
"covered" bigint,
32+
"linesinstrumented" bigint[],
33+
"hitcounts" bigint[],
34+
PRIMARY KEY
35+
(session, manager, filepath) );')
36+
gcloud spanner databases ddl update $db --instance=syzbot --project=syzkaller \
37+
--ddl="$create_table"
38+

0 commit comments

Comments
 (0)