blob: df33baa8b5867e0dc7ab964efecb38ebee641e38 [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
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()
Colin Crosse0df1a32019-06-09 19:40:08 -0700180
181 // Write lets StatusOutput implement io.Writer
182 Write(p []byte) (n int, err error)
Dan Willemsenb82471a2018-05-17 16:37:09 -0700183}
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
Dan Willemsen7f30c072019-01-02 12:50:49 -0800270func (s *Status) Status(msg string) {
271 s.message(StatusLvl, msg)
272}
273
Dan Willemsenb82471a2018-05-17 16:37:09 -0700274type 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}