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