Skip to content

Commit 3fa59dd

Browse files
committed
dashboard/dashapi: AI patching jobs
1 parent c13c05f commit 3fa59dd

File tree

10 files changed

+264
-1
lines changed

10 files changed

+264
-1
lines changed

dashboard/app/ai.go

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
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+
"reflect"
11+
"slices"
12+
13+
"cloud.google.com/go/spanner"
14+
"github.com/google/syzkaller/dashboard/app/aidb"
15+
"github.com/google/syzkaller/dashboard/dashapi"
16+
"google.golang.org/api/iterator"
17+
"google.golang.org/appengine/v2"
18+
"google.golang.org/appengine/v2/log"
19+
)
20+
21+
type uiAIPage struct {
22+
Header *uiHeader
23+
Workflows *aidb.Workflows
24+
}
25+
26+
func handleAIPage(ctx context.Context, w http.ResponseWriter, r *http.Request) error {
27+
hdr, err := commonHeader(ctx, r, w, "")
28+
if err != nil {
29+
return err
30+
}
31+
workflows, err := fetchAIConfig(ctx)
32+
if err != nil {
33+
return err
34+
}
35+
page := &uiAIPage{
36+
Header: hdr,
37+
Workflows: workflows,
38+
}
39+
return serveTemplate(w, "ai.html", page)
40+
}
41+
42+
func apiAIJobPoll(ctx context.Context, req *dashapi.AIJobPollReq) (any, error) {
43+
if err := storeAIWorkflows(ctx, req); err != nil {
44+
log.Errorf(ctx, "storeAIWorkflows: %v", err)
45+
}
46+
resp := &dashapi.AIJobPollResp{}
47+
return resp, nil
48+
}
49+
50+
func apiAIJobDone(ctx context.Context, req *dashapi.AIJobDoneReq) (any, error) {
51+
return nil, nil
52+
}
53+
54+
func apiAIJournal(ctx context.Context, req *dashapi.AIJournalReq) (any, error) {
55+
return nil, nil
56+
}
57+
58+
func storeAIWorkflows(ctx context.Context, req *dashapi.AIJobPollReq) error {
59+
have, err := fetchAIConfig(ctx)
60+
if err != nil {
61+
return err
62+
}
63+
workflows := &aidb.Workflows{
64+
Namespaces: req.Namespaces,
65+
Workflows: req.Workflows,
66+
}
67+
slices.Sort(workflows.Namespaces)
68+
slices.Sort(workflows.Workflows)
69+
if reflect.DeepEqual(have, workflows) {
70+
return nil
71+
}
72+
client, err := aiDBClient(ctx)
73+
if err != nil {
74+
return err
75+
}
76+
defer client.Close()
77+
mut, err := spanner.InsertOrUpdateStruct("workflows", workflows)
78+
if err != nil {
79+
return err
80+
}
81+
_, err = client.Apply(ctx, []*spanner.Mutation{mut})
82+
return err
83+
}
84+
85+
func fetchAIConfig(ctx context.Context) (*aidb.Workflows, error) {
86+
client, err := aiDBClient(ctx)
87+
if err != nil {
88+
return nil, err
89+
}
90+
defer client.Close()
91+
iter := client.Single().Query(ctx, spanner.Statement{
92+
SQL: `select * from workflows where pk=0`,
93+
//Params: map[string]any{},
94+
})
95+
defer iter.Stop()
96+
row, err := iter.Next()
97+
v := new(aidb.Workflows)
98+
if err != nil {
99+
if err != iterator.Done {
100+
return nil, fmt.Errorf("fetchAIConfig: iter.Next: %w", err)
101+
}
102+
} else if err := row.ToStruct(v); err != nil {
103+
return nil, fmt.Errorf("fetchAIConfig: ToStruct: %w", err)
104+
}
105+
return v, nil
106+
}
107+
108+
func aiDBClient(ctx context.Context) (*spanner.Client, error) {
109+
database := "projects/" + appengine.AppID(ctx) + "/instances/syzbot/databases/ai"
110+
return spanner.NewClient(ctx, database)
111+
}

dashboard/app/aidb/entities.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
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 Workflows struct {
7+
PK int64 // fake primary key (always set to 0)
8+
Namespaces []string
9+
Workflows []string
10+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
DROP TABLE workflows;
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
CREATE TABLE workflows (
2+
pk int64,
3+
managers array<string(1000)>,
4+
workflows array<string(1000)>,
5+
) PRIMARY KEY (pk)

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/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+

dashboard/app/main.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"fmt"
1111
"html/template"
1212
"net/http"
13+
_ "net/http/pprof"
1314
"net/url"
1415
"os"
1516
"regexp"
@@ -82,6 +83,7 @@ func initHTTPHandlers() {
8283
http.Handle("/"+ns+"/backports", handlerWrapper(handleBackports))
8384
http.Handle("/"+ns+"/s/", handlerWrapper(handleSubsystemPage))
8485
http.Handle("/"+ns+"/manager/", handlerWrapper(handleManagerPage))
86+
http.Handle("/"+ns+"/ai/", handlerWrapper(handleAIPage))
8587
}
8688
http.HandleFunc("/cron/cache_update", cacheUpdate)
8789
http.HandleFunc("/cron/minute_cache_update", handleMinuteCacheUpdate)

dashboard/app/templates/ai.html

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{{/*
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+
AI workflows page.
6+
*/}}
7+
8+
<!doctype html>
9+
<html>
10+
<head>
11+
{{template "head" .Header}}
12+
<title>syzbot</title>
13+
</head>
14+
<body>
15+
{{template "header" .Header}}
16+
17+
Namespaces:
18+
{{range $ns := $.Workflows.Namespaces}}
19+
{{$ns}} <br>
20+
{{end}}
21+
<br>
22+
23+
Workflows:
24+
{{range $wf := $.Workflows.Workflows}}
25+
{{$wf}} <br>
26+
{{end}}
27+
</body>
28+
</html>

dashboard/dashapi/dashapi.go

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"reflect"
1818
"time"
1919

20+
"github.com/google/syzkaller/pkg/aflow/journal"
2021
"github.com/google/syzkaller/pkg/auth"
2122
)
2223

@@ -988,6 +989,70 @@ type JobInfo struct {
988989
OnMergeBase bool
989990
}
990991

992+
type AIJobPollReq struct {
993+
Namespaces []string
994+
Workflows []string //AIWorkflow
995+
}
996+
997+
type AIWorkflow struct {
998+
Type AIWorkflowType
999+
Name string
1000+
}
1001+
1002+
type AIWorkflowType string
1003+
1004+
const (
1005+
AIPatching AIWorkflowType = "patching"
1006+
)
1007+
1008+
type AIJobPollResp struct {
1009+
ID string
1010+
Workflow AIWorkflow
1011+
Patching *AIPatchingJob
1012+
}
1013+
1014+
type AIJobDoneReq struct {
1015+
ID string
1016+
Error string
1017+
Patching *AIPatchingResult
1018+
}
1019+
1020+
type AIPatchingJob struct {
1021+
ReproOpts string
1022+
ReproSyz string
1023+
ReproC string
1024+
KernelConfig string
1025+
SyzkallerCommit string
1026+
}
1027+
1028+
type AIPatchingResult struct {
1029+
PatchDescription string
1030+
PatchDiff string
1031+
}
1032+
1033+
type AIJournalReq struct {
1034+
JobID string
1035+
Event journal.Event
1036+
//!!! what to do with retried/duplicated entries
1037+
//!!! Attempt int?
1038+
}
1039+
1040+
func (dash *Dashboard) AIJobPoll(req *AIJobPollReq) (*AIJobPollResp, error) {
1041+
resp := new(AIJobPollResp)
1042+
if err := dash.Query("ai_job_poll", req, resp); err != nil {
1043+
return nil, err
1044+
}
1045+
return resp, nil
1046+
}
1047+
1048+
func (dash *Dashboard) AIJobDone(req *AIJobDoneReq) error {
1049+
return dash.Query("ai_job_done", req, nil)
1050+
}
1051+
1052+
func (dash *Dashboard) AIJournal(req *AIJournalReq) error {
1053+
return dash.Query("ai_journal", req, nil)
1054+
}
1055+
9911056
func (dash *Dashboard) Query(method string, req, reply any) error {
9921057
if dash.logger != nil {
9931058
dash.logger("API(%v): %#v", method, req)

0 commit comments

Comments
 (0)