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