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