go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/cv/internal/userhtml/log.go (about)

     1  // Copyright 2022 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package userhtml
    16  
    17  import (
    18  	"fmt"
    19  	"strings"
    20  	"time"
    21  
    22  	bbutil "go.chromium.org/luci/buildbucket/protoutil"
    23  	"go.chromium.org/luci/common/errors"
    24  
    25  	"go.chromium.org/luci/cv/internal/run"
    26  	"go.chromium.org/luci/cv/internal/tryjob"
    27  )
    28  
    29  type uiLogEntry struct {
    30  	runLog    *run.LogEntry
    31  	tryjobLog *tryjob.ExecutionLogEntry
    32  
    33  	// Context info to help render message
    34  	run *run.Run
    35  	cls []*run.RunCL
    36  }
    37  
    38  func (ul *uiLogEntry) HasTryjobChips() bool {
    39  	switch {
    40  	case ul.runLog != nil: // legacy CQDaemon way
    41  		return ul.runLog.GetTryjobsUpdated() != nil
    42  	case ul.tryjobLog != nil:
    43  		switch ul.tryjobLog.GetKind().(type) {
    44  		case *tryjob.ExecutionLogEntry_TryjobsLaunched_:
    45  		case *tryjob.ExecutionLogEntry_TryjobsReused_:
    46  		case *tryjob.ExecutionLogEntry_TryjobsEnded_:
    47  		case *tryjob.ExecutionLogEntry_TryjobDiscarded_:
    48  		case *tryjob.ExecutionLogEntry_RetryDenied_:
    49  		default:
    50  			return false
    51  		}
    52  		return true
    53  	default:
    54  		panic(fmt.Errorf("either Run Log or Tryjob Execution Log must be present"))
    55  	}
    56  }
    57  
    58  func (ul *uiLogEntry) Time() time.Time {
    59  	switch {
    60  	case ul.runLog != nil:
    61  		return ul.runLog.GetTime().AsTime().UTC()
    62  	case ul.tryjobLog != nil:
    63  		return ul.tryjobLog.GetTime().AsTime().UTC()
    64  	default:
    65  		panic(fmt.Errorf("either Run Log or Tryjob Execution Log must be present"))
    66  	}
    67  }
    68  
    69  func (ul *uiLogEntry) EventType() string {
    70  	switch {
    71  	case ul.runLog != nil:
    72  		return runLogEventName(ul.runLog)
    73  	case ul.tryjobLog != nil:
    74  		return tryjobLogEventName(ul.tryjobLog)
    75  	default:
    76  		panic(fmt.Errorf("either Run Log or Tryjob Execution Log must be present"))
    77  	}
    78  }
    79  
    80  func runLogEventName(rle *run.LogEntry) string {
    81  	switch v := rle.GetKind().(type) {
    82  	case *run.LogEntry_TryjobsUpdated_:
    83  		return "Tryjob Updated"
    84  	case *run.LogEntry_ConfigChanged_:
    85  		return "Config Changed"
    86  	case *run.LogEntry_Started_:
    87  		return "Run Started"
    88  	case *run.LogEntry_Created_:
    89  		return "Run Created"
    90  	case *run.LogEntry_TreeChecked_:
    91  		return "Tree Status Check"
    92  	case *run.LogEntry_Info_:
    93  		return v.Info.Label
    94  	case *run.LogEntry_AcquiredSubmitQueue_:
    95  		return "Acquired Submit Queue"
    96  	case *run.LogEntry_ReleasedSubmitQueue_:
    97  		return "Released Submit Queue"
    98  	case *run.LogEntry_Waitlisted_:
    99  		return "Waiting For Submission"
   100  	case *run.LogEntry_SubmissionFailure_:
   101  		return "CL Submission Failed"
   102  	case *run.LogEntry_ClSubmitted:
   103  		return "CL Submission"
   104  	case *run.LogEntry_RunEnded_:
   105  		return "Run Ended"
   106  	default:
   107  		panic(fmt.Errorf("unknown Run log kind %T", v))
   108  	}
   109  }
   110  
   111  func tryjobLogEventName(tle *tryjob.ExecutionLogEntry) string {
   112  	switch v := tle.GetKind().(type) {
   113  	case *tryjob.ExecutionLogEntry_RequirementChanged_:
   114  		return "Tryjob Requirement Changed"
   115  	case *tryjob.ExecutionLogEntry_TryjobsLaunched_:
   116  		return "Tryjob Launched"
   117  	case *tryjob.ExecutionLogEntry_TryjobsLaunchFailed_:
   118  		return "Tryjob Launch Failure"
   119  	case *tryjob.ExecutionLogEntry_TryjobsReused_:
   120  		return "Tryjob Reused"
   121  	case *tryjob.ExecutionLogEntry_TryjobsEnded_:
   122  		return "Tryjob Ended"
   123  	case *tryjob.ExecutionLogEntry_TryjobDiscarded_:
   124  		return "Tryjob Discarded"
   125  	case *tryjob.ExecutionLogEntry_RetryDenied_:
   126  		return "Retry Denied"
   127  	default:
   128  		panic(fmt.Errorf("unknown Tryjob execution log kind %T", v))
   129  	}
   130  }
   131  
   132  func (ul *uiLogEntry) Message() string {
   133  	switch {
   134  	case ul.runLog != nil:
   135  		return ul.runLogMessage(ul.runLog)
   136  	case ul.tryjobLog != nil:
   137  		return tryjobLogMessage(ul.tryjobLog)
   138  	default:
   139  		panic(fmt.Errorf("either Run Log or Tryjob Execution Log must be present"))
   140  	}
   141  }
   142  
   143  func (ul *uiLogEntry) runLogMessage(rle *run.LogEntry) string {
   144  	switch v := rle.GetKind().(type) {
   145  	case *run.LogEntry_Info_:
   146  		return v.Info.Message
   147  	case *run.LogEntry_ClSubmitted:
   148  		return StringifySubmissionSuccesses(ul.cls, v.ClSubmitted)
   149  	case *run.LogEntry_SubmissionFailure_:
   150  		return StringifySubmissionFailureReason(ul.cls, v.SubmissionFailure.Event)
   151  	case *run.LogEntry_RunEnded_:
   152  		// This assumes the status of a Run won't change after transitioning to
   153  		// one of the terminal statuses. If the assumption is no longer valid.
   154  		// End status and cancellation reason need to be stored explicitly in
   155  		// the log entry.
   156  		if ul.run.Status == run.Status_CANCELLED {
   157  			switch len(ul.run.CancellationReasons) {
   158  			case 0:
   159  			case 1:
   160  				return fmt.Sprintf("Run is cancelled. Reason: %s", ul.run.CancellationReasons[0])
   161  			default:
   162  				var sb strings.Builder
   163  				sb.WriteString("Run is cancelled. Reasons:")
   164  				for _, reason := range ul.run.CancellationReasons {
   165  					sb.WriteRune('\n')
   166  					sb.WriteString("  * ")
   167  					sb.WriteString(strings.TrimSpace(reason))
   168  				}
   169  				return sb.String()
   170  			}
   171  		}
   172  		return ""
   173  	default:
   174  		return ""
   175  	}
   176  }
   177  
   178  func tryjobLogMessage(tle *tryjob.ExecutionLogEntry) string {
   179  	switch v := tle.GetKind().(type) {
   180  	case *tryjob.ExecutionLogEntry_RequirementChanged_:
   181  		// TODO(yiwzhang): describe what has changed.
   182  		return ""
   183  	case *tryjob.ExecutionLogEntry_TryjobsLaunchFailed_:
   184  		var sb strings.Builder
   185  		for i, tj := range v.TryjobsLaunchFailed.GetTryjobs() {
   186  			if i != 0 {
   187  				sb.WriteString("\n")
   188  			}
   189  			sb.WriteString("* ")
   190  			sb.WriteString(bbutil.FormatBuilderID(tj.Definition.GetBuildbucket().GetBuilder()))
   191  			sb.WriteString(": ")
   192  			sb.WriteString(tj.GetReason())
   193  		}
   194  		return sb.String()
   195  	case *tryjob.ExecutionLogEntry_TryjobDiscarded_:
   196  		return fmt.Sprintf("Reason: %s", v.TryjobDiscarded.GetReason())
   197  	case *tryjob.ExecutionLogEntry_RetryDenied_:
   198  		return fmt.Sprintf("Can't retry following tryjob(s) because %s", v.RetryDenied.GetReason())
   199  	default:
   200  		return ""
   201  	}
   202  }
   203  
   204  func (ul *uiLogEntry) LegacyTryjobsByStatus() map[string][]*uiTryjob {
   205  	if ul.runLog.GetTryjobsUpdated() == nil {
   206  		return nil
   207  	}
   208  	tjs := ul.runLog.GetTryjobsUpdated().GetTryjobs()
   209  	ret := make(map[string][]*uiTryjob, len(tjs))
   210  	for _, tj := range tjs {
   211  		k := strings.Title(strings.ToLower(tj.Status.String()))
   212  		ret[k] = append(ret[k], &uiTryjob{
   213  			ExternalID: tryjob.ExternalID(tj.ExternalId),
   214  			Definition: tj.Definition,
   215  			Status:     tj.Status,
   216  			Result:     tj.Result,
   217  			Reused:     tj.Reused,
   218  		})
   219  	}
   220  	return ret
   221  }
   222  
   223  func (ul *uiLogEntry) Tryjobs() []*uiTryjob {
   224  	if !ul.HasTryjobChips() {
   225  		panic(errors.Reason("requested tryjobs for log entry that doesn't have tryjob chip %T", ul.tryjobLog.GetKind()).Err())
   226  	}
   227  	switch v := ul.tryjobLog.GetKind().(type) {
   228  	case *tryjob.ExecutionLogEntry_TryjobsLaunched_:
   229  		return makeUITryjobsFromSnapshots(v.TryjobsLaunched.GetTryjobs())
   230  	case *tryjob.ExecutionLogEntry_TryjobsReused_:
   231  		return makeUITryjobsFromSnapshots(v.TryjobsReused.GetTryjobs())
   232  	case *tryjob.ExecutionLogEntry_TryjobsEnded_:
   233  		return makeUITryjobsFromSnapshots(v.TryjobsEnded.GetTryjobs())
   234  	case *tryjob.ExecutionLogEntry_TryjobDiscarded_:
   235  		return makeUITryjobsFromSnapshots([]*tryjob.ExecutionLogEntry_TryjobSnapshot{v.TryjobDiscarded.GetSnapshot()})
   236  	case *tryjob.ExecutionLogEntry_RetryDenied_:
   237  		return makeUITryjobsFromSnapshots(v.RetryDenied.GetTryjobs())
   238  	default:
   239  		panic(fmt.Errorf("not supported tryjob log kind %T", v))
   240  	}
   241  }