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 }