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