github.com/google/syzkaller@v0.0.0-20251211124644-a066d2bc4b02/dashboard/app/batch_coverage.go (about) 1 // Copyright 2017 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 "strconv" 11 12 "cloud.google.com/go/batch/apiv1/batchpb" 13 "cloud.google.com/go/bigquery" 14 "cloud.google.com/go/civil" 15 "github.com/google/syzkaller/pkg/coveragedb" 16 "google.golang.org/api/iterator" 17 "google.golang.org/appengine/v2/log" 18 ) 19 20 const batchCoverageTimeoutSeconds = 60 * 60 * 12 21 22 func handleBatchCoverage(w http.ResponseWriter, r *http.Request) { 23 ctx := r.Context() 24 doQuarters := r.FormValue("quarters") == "true" 25 doMonths := r.FormValue("months") == "true" 26 doDays := r.FormValue("days") == "true" 27 maxSteps, err := strconv.Atoi(r.FormValue("steps")) 28 if err != nil { 29 log.Errorf(ctx, "failed to convert &steps= into maxSteps: %s", err.Error()) 30 return 31 } 32 for ns, nsConfig := range getConfig(ctx).Namespaces { 33 if nsConfig.Coverage == nil { 34 continue 35 } 36 repo, branch := nsConfig.mainRepoBranch() 37 if repo == "" || branch == "" { 38 log.Errorf(ctx, "can't find default repo or branch for ns %s", ns) 39 continue 40 } 41 daysAvailable, rowsAvailable, err := nsDataAvailable(ctx, ns) 42 if err != nil { 43 log.Errorf(ctx, "failed nsDataAvailable(%s): %s", ns, err) 44 } 45 periodsMerged, rowsMerged, err := coveragedb.NsDataMerged(ctx, coverageDBClient, ns) 46 if err != nil { 47 log.Errorf(ctx, "failed coveragedb.NsDataMerged(%s): %s", ns, err) 48 } 49 var periods []coveragedb.TimePeriod 50 if doDays { 51 periods = append(periods, coveragedb.PeriodsToMerge(daysAvailable, periodsMerged, rowsAvailable, rowsMerged, 52 &coveragedb.DayPeriodOps{})...) 53 } 54 if doMonths { 55 periods = append(periods, coveragedb.PeriodsToMerge(daysAvailable, periodsMerged, rowsAvailable, rowsMerged, 56 &coveragedb.MonthPeriodOps{})...) 57 } 58 if doQuarters { 59 periods = append(periods, coveragedb.PeriodsToMerge(daysAvailable, periodsMerged, rowsAvailable, rowsMerged, 60 &coveragedb.QuarterPeriodOps{})...) 61 } 62 if len(periods) == 0 { 63 log.Infof(ctx, "there is no new coverage for merging available in %s", ns) 64 continue 65 } 66 periods = coveragedb.AtMostNLatestPeriods(periods, maxSteps) 67 nsCovConfig := nsConfig.Coverage 68 serviceAccount := &batchpb.ServiceAccount{ 69 Email: nsCovConfig.BatchServiceAccount, 70 Scopes: nsCovConfig.BatchScopes, 71 } 72 if err := createScriptJob(ctx, nsCovConfig.BatchProject, "coverage-merge", 73 batchCoverageScript(ns, repo, branch, periods, 74 nsCovConfig.JobInitScript, 75 nsCovConfig.SyzEnvInitScript, 76 nsCovConfig.DashboardClientName), 77 batchCoverageTimeoutSeconds, 78 serviceAccount, 79 ); err != nil { 80 log.Errorf(ctx, "failed to batchCoverageScript: %s", err.Error()) 81 } 82 } 83 } 84 85 func batchCoverageScript(ns, repo, branch string, periods []coveragedb.TimePeriod, 86 jobInitScript, syzEnvInitScript, clientName string) string { 87 if clientName == "" { 88 clientName = defaultDashboardClientName 89 } 90 script := jobInitScript + "\n" 91 script += "git clone -q --depth 1 --branch master --single-branch https://github.com/google/syzkaller\n" + 92 "cd syzkaller\n" + 93 "export CI=1\n" + 94 "./tools/syz-env \"" 95 if syzEnvInitScript != "" { 96 script += syzEnvInitScript + "; " 97 } 98 for _, period := range periods { 99 script += "./tools/syz-bq.sh" + 100 " -w ../workdir-cover-aggregation/" + 101 " -n " + ns + 102 " -r " + repo + 103 " -b " + branch + 104 " -d " + strconv.Itoa(period.Days) + 105 " -t " + period.DateTo.String() + 106 " -c " + clientName + 107 " 2>&1; " // we don't want stderr output to be logged as errors 108 } 109 script += "\"" 110 return script 111 } 112 113 func nsDataAvailable(ctx context.Context, ns string) ([]coveragedb.TimePeriod, []int64, error) { 114 client, err := bigquery.NewClient(ctx, "syzkaller") 115 if err != nil { 116 return nil, nil, fmt.Errorf("failed to initialize bigquery client: %w", err) 117 } 118 if err := client.EnableStorageReadClient(ctx); err != nil { 119 return nil, nil, fmt.Errorf("failed to client.EnableStorageReadClient: %w", err) 120 } 121 q := client.Query(fmt.Sprintf(` 122 SELECT 123 PARSE_DATE('%%Y%%m%%d', partition_id) as partitiondate, 124 total_rows as records 125 FROM 126 syzkaller.syzbot_coverage.INFORMATION_SCHEMA.PARTITIONS 127 WHERE table_name LIKE '%s' 128 `, ns)) 129 it, err := q.Read(ctx) 130 if err != nil { 131 return nil, nil, fmt.Errorf("failed to Read() from bigquery: %w", err) 132 } 133 134 var periods []coveragedb.TimePeriod 135 var recordsCount []int64 136 for { 137 var values struct { 138 PartitionDate civil.Date 139 Records int64 140 } 141 err = it.Next(&values) 142 if err == iterator.Done { 143 break 144 } 145 if err != nil { 146 return nil, nil, fmt.Errorf("failed to it.Next() bigquery records: %w", err) 147 } 148 periods = append(periods, coveragedb.TimePeriod{DateTo: values.PartitionDate, Days: 1}) 149 recordsCount = append(recordsCount, values.Records) 150 } 151 return periods, recordsCount, nil 152 } 153 154 func handleBatchCoverageClean(w http.ResponseWriter, r *http.Request) { 155 ctx := r.Context() 156 totalDeleted, err := coveragedb.DeleteGarbage(ctx, coverageDBClient) 157 if err != nil { 158 errMsg := fmt.Sprintf("failed to coveragedb.DeleteGarbage: %s", err.Error()) 159 log.Errorf(ctx, "%s", errMsg) 160 w.Write([]byte(errMsg)) 161 return 162 } 163 logMsg := fmt.Sprintf("successfully deleted %d rows\n", totalDeleted) 164 log.Infof(ctx, "%s", logMsg) 165 w.Write([]byte(logMsg)) 166 }