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