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  }