blob: 52ed56a1379e2085f39e69f99c5818a2c585072e [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
Yu Liua58467a2024-03-05 01:12:19 +000044
45 // ChangedInputs is the (optional) list of inputs that have changed
46 // since last time this action was run.
47 ChangedInputs []string
Dan Willemsenb82471a2018-05-17 16:37:09 -070048}
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
Colin Crossd888b6b2020-10-15 13:46:32 -070062
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
Dan Alberte82234e2023-06-01 23:09:38 +000093
94 Tags string
Dan Willemsenb82471a2018-05-17 16:37:09 -070095}
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
Jeongik Cha3622b342023-11-27 11:00:52 +0900115
116 EstimatedTime time.Time
Dan Willemsenb82471a2018-05-17 16:37:09 -0700117}
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)
Jeongik Cha3622b342023-11-27 11:00:52 +0900128 SetEstimatedTime(estimatedTime time.Time)
Dan Willemsenb82471a2018-05-17 16:37:09 -0700129
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()
Colin Crosse0df1a32019-06-09 19:40:08 -0700221
222 // Write lets StatusOutput implement io.Writer
223 Write(p []byte) (n int, err error)
Dan Willemsenb82471a2018-05-17 16:37:09 -0700224}
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
Jeongik Cha3622b342023-11-27 11:00:52 +0900278func (s *Status) SetEstimatedTime(estimatedTime time.Time) {
279 s.lock.Lock()
280 defer s.lock.Unlock()
281
282 s.counts.EstimatedTime = estimatedTime
283}
284
Dan Willemsenb82471a2018-05-17 16:37:09 -0700285func (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
Dan Willemsen7f30c072019-01-02 12:50:49 -0800318func (s *Status) Status(msg string) {
319 s.message(StatusLvl, msg)
320}
321
Dan Willemsenb82471a2018-05-17 16:37:09 -0700322type 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
Jeongik Cha3622b342023-11-27 11:00:52 +0900347func (d *toolStatus) SetEstimatedTime(estimatedTime time.Time) {
348 d.status.SetEstimatedTime(estimatedTime)
349}
350
Dan Willemsenb82471a2018-05-17 16:37:09 -0700351func (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}