1// Copyright 2018 Google Inc. All rights reserved. 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 status tracks actions run by various tools, combining the counts 16// (total actions, currently running, started, finished), and giving that to 17// multiple outputs. 18package status 19 20import ( 21 "sync" 22) 23 24// Action describes an action taken (or as Ninja calls them, Edges). 25type Action struct { 26 // Description is a shorter, more readable form of the command, meant 27 // for users. It's optional, but one of either Description or Command 28 // should be set. 29 Description string 30 31 // Outputs is the (optional) list of outputs. Usually these are files, 32 // but they can be any string. 33 Outputs []string 34 35 // Command is the actual command line executed to perform the action. 36 // It's optional, but one of either Description or Command should be 37 // set. 38 Command string 39} 40 41// ActionResult describes the result of running an Action. 42type ActionResult struct { 43 // Action is a pointer to the original Action struct. 44 *Action 45 46 // Output is the output produced by the command (usually stdout&stderr 47 // for Actions that run commands) 48 Output string 49 50 // Error is nil if the Action succeeded, or set to an error if it 51 // failed. 52 Error error 53} 54 55// Counts describes the number of actions in each state 56type Counts struct { 57 // TotalActions is the total number of expected changes. This can 58 // generally change up or down during a build, but it should never go 59 // below the number of StartedActions 60 TotalActions int 61 62 // RunningActions are the number of actions that are currently running 63 // -- the number that have called StartAction, but not FinishAction. 64 RunningActions int 65 66 // StartedActions are the number of actions that have been started with 67 // StartAction. 68 StartedActions int 69 70 // FinishedActions are the number of actions that have been finished 71 // with FinishAction. 72 FinishedActions int 73} 74 75// ToolStatus is the interface used by tools to report on their Actions, and to 76// present other information through a set of messaging functions. 77type ToolStatus interface { 78 // SetTotalActions sets the expected total number of actions that will 79 // be started by this tool. 80 // 81 // This call be will ignored if it sets a number that is less than the 82 // current number of started actions. 83 SetTotalActions(total int) 84 85 // StartAction specifies that the associated action has been started by 86 // the tool. 87 // 88 // A specific *Action should not be specified to StartAction more than 89 // once, even if the previous action has already been finished, and the 90 // contents rewritten. 91 // 92 // Do not re-use *Actions between different ToolStatus interfaces 93 // either. 94 StartAction(action *Action) 95 96 // FinishAction specifies the result of a particular Action. 97 // 98 // The *Action embedded in the ActionResult structure must have already 99 // been passed to StartAction (on this interface). 100 // 101 // Do not call FinishAction twice for the same *Action. 102 FinishAction(result ActionResult) 103 104 // Verbose takes a non-important message that is never printed to the 105 // screen, but is in the verbose build log, etc 106 Verbose(msg string) 107 // Status takes a less important message that may be printed to the 108 // screen, but overwritten by another status message. The full message 109 // will still appear in the verbose build log. 110 Status(msg string) 111 // Print takes an message and displays it to the screen and other 112 // output logs, etc. 113 Print(msg string) 114 // Error is similar to Print, but treats it similarly to a failed 115 // action, showing it in the error logs, etc. 116 Error(msg string) 117 118 // Finish marks the end of all Actions being run by this tool. 119 // 120 // SetTotalEdges, StartAction, and FinishAction should not be called 121 // after Finish. 122 Finish() 123} 124 125// MsgLevel specifies the importance of a particular log message. See the 126// descriptions in ToolStatus: Verbose, Status, Print, Error. 127type MsgLevel int 128 129const ( 130 VerboseLvl MsgLevel = iota 131 StatusLvl 132 PrintLvl 133 ErrorLvl 134) 135 136func (l MsgLevel) Prefix() string { 137 switch l { 138 case VerboseLvl: 139 return "verbose: " 140 case StatusLvl: 141 return "status: " 142 case PrintLvl: 143 return "" 144 case ErrorLvl: 145 return "error: " 146 default: 147 panic("Unknown message level") 148 } 149} 150 151// StatusOutput is the interface used to get status information as a Status 152// output. 153// 154// All of the functions here are guaranteed to be called by Status while 155// holding it's internal lock, so it's safe to assume a single caller at any 156// time, and that the ordering of calls will be correct. It is not safe to call 157// back into the Status, or one of its ToolStatus interfaces. 158type StatusOutput interface { 159 // StartAction will be called once every time ToolStatus.StartAction is 160 // called. counts will include the current counters across all 161 // ToolStatus instances, including ones that have been finished. 162 StartAction(action *Action, counts Counts) 163 164 // FinishAction will be called once every time ToolStatus.FinishAction 165 // is called. counts will include the current counters across all 166 // ToolStatus instances, including ones that have been finished. 167 FinishAction(result ActionResult, counts Counts) 168 169 // Message is the equivalent of ToolStatus.Verbose/Status/Print/Error, 170 // but the level is specified as an argument. 171 Message(level MsgLevel, msg string) 172 173 // Flush is called when your outputs should be flushed / closed. No 174 // output is expected after this call. 175 Flush() 176} 177 178// Status is the multiplexer / accumulator between ToolStatus instances (via 179// StartTool) and StatusOutputs (via AddOutput). There's generally one of these 180// per build process (though tools like multiproduct_kati may have multiple 181// independent versions). 182type Status struct { 183 counts Counts 184 outputs []StatusOutput 185 186 // Protects counts and outputs, and allows each output to 187 // expect only a single caller at a time. 188 lock sync.Mutex 189} 190 191// AddOutput attaches an output to this object. It's generally expected that an 192// output is attached to a single Status instance. 193func (s *Status) AddOutput(output StatusOutput) { 194 if output == nil { 195 return 196 } 197 198 s.lock.Lock() 199 defer s.lock.Unlock() 200 201 s.outputs = append(s.outputs, output) 202} 203 204// StartTool returns a new ToolStatus instance to report the status of a tool. 205func (s *Status) StartTool() ToolStatus { 206 return &toolStatus{ 207 status: s, 208 } 209} 210 211// Finish will call Flush on all the outputs, generally flushing or closing all 212// of their outputs. Do not call any other functions on this instance or any 213// associated ToolStatus instances after this has been called. 214func (s *Status) Finish() { 215 s.lock.Lock() 216 defer s.lock.Unlock() 217 218 for _, o := range s.outputs { 219 o.Flush() 220 } 221} 222 223func (s *Status) updateTotalActions(diff int) { 224 s.lock.Lock() 225 defer s.lock.Unlock() 226 227 s.counts.TotalActions += diff 228} 229 230func (s *Status) startAction(action *Action) { 231 s.lock.Lock() 232 defer s.lock.Unlock() 233 234 s.counts.RunningActions += 1 235 s.counts.StartedActions += 1 236 237 for _, o := range s.outputs { 238 o.StartAction(action, s.counts) 239 } 240} 241 242func (s *Status) finishAction(result ActionResult) { 243 s.lock.Lock() 244 defer s.lock.Unlock() 245 246 s.counts.RunningActions -= 1 247 s.counts.FinishedActions += 1 248 249 for _, o := range s.outputs { 250 o.FinishAction(result, s.counts) 251 } 252} 253 254func (s *Status) message(level MsgLevel, msg string) { 255 s.lock.Lock() 256 defer s.lock.Unlock() 257 258 for _, o := range s.outputs { 259 o.Message(level, msg) 260 } 261} 262 263func (s *Status) Status(msg string) { 264 s.message(StatusLvl, msg) 265} 266 267type toolStatus struct { 268 status *Status 269 270 counts Counts 271 // Protects counts 272 lock sync.Mutex 273} 274 275var _ ToolStatus = (*toolStatus)(nil) 276 277func (d *toolStatus) SetTotalActions(total int) { 278 diff := 0 279 280 d.lock.Lock() 281 if total >= d.counts.StartedActions && total != d.counts.TotalActions { 282 diff = total - d.counts.TotalActions 283 d.counts.TotalActions = total 284 } 285 d.lock.Unlock() 286 287 if diff != 0 { 288 d.status.updateTotalActions(diff) 289 } 290} 291 292func (d *toolStatus) StartAction(action *Action) { 293 totalDiff := 0 294 295 d.lock.Lock() 296 d.counts.RunningActions += 1 297 d.counts.StartedActions += 1 298 299 if d.counts.StartedActions > d.counts.TotalActions { 300 totalDiff = d.counts.StartedActions - d.counts.TotalActions 301 d.counts.TotalActions = d.counts.StartedActions 302 } 303 d.lock.Unlock() 304 305 if totalDiff != 0 { 306 d.status.updateTotalActions(totalDiff) 307 } 308 d.status.startAction(action) 309} 310 311func (d *toolStatus) FinishAction(result ActionResult) { 312 d.lock.Lock() 313 d.counts.RunningActions -= 1 314 d.counts.FinishedActions += 1 315 d.lock.Unlock() 316 317 d.status.finishAction(result) 318} 319 320func (d *toolStatus) Verbose(msg string) { 321 d.status.message(VerboseLvl, msg) 322} 323func (d *toolStatus) Status(msg string) { 324 d.status.message(StatusLvl, msg) 325} 326func (d *toolStatus) Print(msg string) { 327 d.status.message(PrintLvl, msg) 328} 329func (d *toolStatus) Error(msg string) { 330 d.status.message(ErrorLvl, msg) 331} 332 333func (d *toolStatus) Finish() { 334 d.lock.Lock() 335 defer d.lock.Unlock() 336 337 if d.counts.TotalActions != d.counts.StartedActions { 338 d.status.updateTotalActions(d.counts.StartedActions - d.counts.TotalActions) 339 } 340 341 // TODO: update status to correct running/finished edges? 342 d.counts.RunningActions = 0 343 d.counts.TotalActions = d.counts.StartedActions 344} 345