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