golang.org/x/build@v0.0.0-20240506185731-218518f32b70/cmd/coordinator/internal/dashboard/datastore.go (about)

     1  // Copyright 2020 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  //go:build linux || darwin
     6  
     7  package dashboard
     8  
     9  import (
    10  	"context"
    11  	"errors"
    12  	"fmt"
    13  	"log"
    14  
    15  	"cloud.google.com/go/datastore"
    16  	bbpb "go.chromium.org/luci/buildbucket/proto"
    17  	"golang.org/x/build/cmd/coordinator/internal/lucipoll"
    18  )
    19  
    20  // getDatastoreResults populates result data fetched from Datastore into commits.
    21  func getDatastoreResults(ctx context.Context, cl *datastore.Client, commits []*commit, pkg string) {
    22  	var keys []*datastore.Key
    23  	for _, c := range commits {
    24  		pkey := datastore.NameKey("Package", pkg, nil)
    25  		pkey.Namespace = "Git"
    26  		key := datastore.NameKey("Commit", "|"+c.Hash, pkey)
    27  		key.Namespace = "Git"
    28  		keys = append(keys, key)
    29  	}
    30  	out := make([]*Commit, len(keys))
    31  	// datastore.ErrNoSuchEntity is returned when we ask for a commit that we do not yet have test data.
    32  	if err := cl.GetMulti(ctx, keys, out); err != nil && filterMultiError(err, ignoreNoSuchEntity) != nil {
    33  		log.Printf("getDatastoreResults: error fetching %d results: %v", len(keys), err)
    34  		return
    35  	}
    36  	hashOut := make(map[string]*Commit)
    37  	for _, o := range out {
    38  		if o != nil && o.Hash != "" {
    39  			hashOut[o.Hash] = o
    40  		}
    41  	}
    42  	for _, c := range commits {
    43  		if result, ok := hashOut[c.Hash]; ok {
    44  			c.ResultData = result.ResultData
    45  		}
    46  	}
    47  }
    48  
    49  // appendLUCIResults appends result data polled from LUCI to commits.
    50  func appendLUCIResults(luci lucipoll.Snapshot, commits []*commit, repo string) {
    51  	commitBuilds, ok := luci.RepoCommitBuilds[repo]
    52  	if !ok {
    53  		return
    54  	}
    55  	for _, c := range commits {
    56  		builds, ok := commitBuilds[c.Hash]
    57  		if !ok {
    58  			// No builds for this commit.
    59  			continue
    60  		}
    61  		for _, b := range builds {
    62  			switch b.Status {
    63  			case bbpb.Status_STARTED:
    64  				c.ResultData = append(c.ResultData, fmt.Sprintf("%s|%s",
    65  					b.BuilderName,
    66  					buildURL(b.ID),
    67  				))
    68  			case bbpb.Status_SUCCESS, bbpb.Status_FAILURE:
    69  				c.ResultData = append(c.ResultData, fmt.Sprintf("%s|%t|%s|%s",
    70  					b.BuilderName,
    71  					b.Status == bbpb.Status_SUCCESS,
    72  					buildURL(b.ID),
    73  					c.Hash,
    74  				))
    75  			case bbpb.Status_INFRA_FAILURE:
    76  				c.ResultData = append(c.ResultData, fmt.Sprintf("%s|%s|%s|%s",
    77  					b.BuilderName,
    78  					"infra_failure",
    79  					buildURL(b.ID),
    80  					c.Hash,
    81  				))
    82  			}
    83  		}
    84  	}
    85  }
    86  
    87  func buildURL(buildID int64) string {
    88  	return fmt.Sprintf("https://ci.chromium.org/b/%d", buildID)
    89  }
    90  
    91  type ignoreFunc func(err error) error
    92  
    93  // ignoreNoSuchEntity ignores datastore.ErrNoSuchEntity, which is returned when
    94  // we ask for a commit that we do not yet have test data.
    95  func ignoreNoSuchEntity(err error) error {
    96  	if !errors.Is(err, datastore.ErrNoSuchEntity) {
    97  		return err
    98  	}
    99  	return nil
   100  }
   101  
   102  // filterMultiError loops over datastore.MultiError, skipping errors ignored by
   103  // the specified ignoreFuncs. Any unfiltered errors will be returned as a
   104  // datastore.MultiError error. If no errors are left, nil will be returned.
   105  // Errors that are not datastore.MultiError will be returned as-is.
   106  func filterMultiError(err error, ignores ...ignoreFunc) error {
   107  	if err == nil {
   108  		return nil
   109  	}
   110  	me := datastore.MultiError{}
   111  	if ok := errors.As(err, &me); !ok {
   112  		return err
   113  	}
   114  	ret := datastore.MultiError{}
   115  	for _, err := range me {
   116  		var skip bool
   117  		for _, ignore := range ignores {
   118  			if err := ignore(err); err == nil {
   119  				skip = true
   120  				break
   121  			}
   122  		}
   123  		if !skip {
   124  			ret = append(ret, err)
   125  		}
   126  	}
   127  	if len(ret) > 0 {
   128  		return ret
   129  	}
   130  	return nil
   131  }