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