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