• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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	// 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()
176}
177
178// Status is the multiplexer / accumulator between ToolStatus instances (via
179// StartTool) and StatusOutputs (via AddOutput). There's generally one of these
180// per build process (though tools like multiproduct_kati may have multiple
181// independent versions).
182type Status struct {
183	counts  Counts
184	outputs []StatusOutput
185
186	// Protects counts and outputs, and allows each output to
187	// expect only a single caller at a time.
188	lock sync.Mutex
189}
190
191// AddOutput attaches an output to this object. It's generally expected that an
192// output is attached to a single Status instance.
193func (s *Status) AddOutput(output StatusOutput) {
194	if output == nil {
195		return
196	}
197
198	s.lock.Lock()
199	defer s.lock.Unlock()
200
201	s.outputs = append(s.outputs, output)
202}
203
204// StartTool returns a new ToolStatus instance to report the status of a tool.
205func (s *Status) StartTool() ToolStatus {
206	return &toolStatus{
207		status: s,
208	}
209}
210
211// Finish will call Flush on all the outputs, generally flushing or closing all
212// of their outputs. Do not call any other functions on this instance or any
213// associated ToolStatus instances after this has been called.
214func (s *Status) Finish() {
215	s.lock.Lock()
216	defer s.lock.Unlock()
217
218	for _, o := range s.outputs {
219		o.Flush()
220	}
221}
222
223func (s *Status) updateTotalActions(diff int) {
224	s.lock.Lock()
225	defer s.lock.Unlock()
226
227	s.counts.TotalActions += diff
228}
229
230func (s *Status) startAction(action *Action) {
231	s.lock.Lock()
232	defer s.lock.Unlock()
233
234	s.counts.RunningActions += 1
235	s.counts.StartedActions += 1
236
237	for _, o := range s.outputs {
238		o.StartAction(action, s.counts)
239	}
240}
241
242func (s *Status) finishAction(result ActionResult) {
243	s.lock.Lock()
244	defer s.lock.Unlock()
245
246	s.counts.RunningActions -= 1
247	s.counts.FinishedActions += 1
248
249	for _, o := range s.outputs {
250		o.FinishAction(result, s.counts)
251	}
252}
253
254func (s *Status) message(level MsgLevel, msg string) {
255	s.lock.Lock()
256	defer s.lock.Unlock()
257
258	for _, o := range s.outputs {
259		o.Message(level, msg)
260	}
261}
262
263func (s *Status) Status(msg string) {
264	s.message(StatusLvl, msg)
265}
266
267type toolStatus struct {
268	status *Status
269
270	counts Counts
271	// Protects counts
272	lock sync.Mutex
273}
274
275var _ ToolStatus = (*toolStatus)(nil)
276
277func (d *toolStatus) SetTotalActions(total int) {
278	diff := 0
279
280	d.lock.Lock()
281	if total >= d.counts.StartedActions && total != d.counts.TotalActions {
282		diff = total - d.counts.TotalActions
283		d.counts.TotalActions = total
284	}
285	d.lock.Unlock()
286
287	if diff != 0 {
288		d.status.updateTotalActions(diff)
289	}
290}
291
292func (d *toolStatus) StartAction(action *Action) {
293	totalDiff := 0
294
295	d.lock.Lock()
296	d.counts.RunningActions += 1
297	d.counts.StartedActions += 1
298
299	if d.counts.StartedActions > d.counts.TotalActions {
300		totalDiff = d.counts.StartedActions - d.counts.TotalActions
301		d.counts.TotalActions = d.counts.StartedActions
302	}
303	d.lock.Unlock()
304
305	if totalDiff != 0 {
306		d.status.updateTotalActions(totalDiff)
307	}
308	d.status.startAction(action)
309}
310
311func (d *toolStatus) FinishAction(result ActionResult) {
312	d.lock.Lock()
313	d.counts.RunningActions -= 1
314	d.counts.FinishedActions += 1
315	d.lock.Unlock()
316
317	d.status.finishAction(result)
318}
319
320func (d *toolStatus) Verbose(msg string) {
321	d.status.message(VerboseLvl, msg)
322}
323func (d *toolStatus) Status(msg string) {
324	d.status.message(StatusLvl, msg)
325}
326func (d *toolStatus) Print(msg string) {
327	d.status.message(PrintLvl, msg)
328}
329func (d *toolStatus) Error(msg string) {
330	d.status.message(ErrorLvl, msg)
331}
332
333func (d *toolStatus) Finish() {
334	d.lock.Lock()
335	defer d.lock.Unlock()
336
337	if d.counts.TotalActions != d.counts.StartedActions {
338		d.status.updateTotalActions(d.counts.StartedActions - d.counts.TotalActions)
339	}
340
341	// TODO: update status to correct running/finished edges?
342	d.counts.RunningActions = 0
343	d.counts.TotalActions = d.counts.StartedActions
344}
345