383 lines
10 KiB
Go
383 lines
10 KiB
Go
// Copyright 2018 Google Inc. All rights reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
// Package status tracks actions run by various tools, combining the counts
|
|
// (total actions, currently running, started, finished), and giving that to
|
|
// multiple outputs.
|
|
package status
|
|
|
|
import (
|
|
"sync"
|
|
)
|
|
|
|
// Action describes an action taken (or as Ninja calls them, Edges).
|
|
type Action struct {
|
|
// Description is a shorter, more readable form of the command, meant
|
|
// for users. It's optional, but one of either Description or Command
|
|
// should be set.
|
|
Description string
|
|
|
|
// Outputs is the (optional) list of outputs. Usually these are files,
|
|
// but they can be any string.
|
|
Outputs []string
|
|
|
|
// Inputs is the (optional) list of inputs. Usually these are files,
|
|
// but they can be any string.
|
|
Inputs []string
|
|
|
|
// Command is the actual command line executed to perform the action.
|
|
// It's optional, but one of either Description or Command should be
|
|
// set.
|
|
Command string
|
|
}
|
|
|
|
// ActionResult describes the result of running an Action.
|
|
type ActionResult struct {
|
|
// Action is a pointer to the original Action struct.
|
|
*Action
|
|
|
|
// Output is the output produced by the command (usually stdout&stderr
|
|
// for Actions that run commands)
|
|
Output string
|
|
|
|
// Error is nil if the Action succeeded, or set to an error if it
|
|
// failed.
|
|
Error error
|
|
|
|
Stats ActionResultStats
|
|
}
|
|
|
|
type ActionResultStats struct {
|
|
// Number of milliseconds spent executing in user mode
|
|
UserTime uint32
|
|
|
|
// Number of milliseconds spent executing in kernel mode
|
|
SystemTime uint32
|
|
|
|
// Max resident set size in kB
|
|
MaxRssKB uint64
|
|
|
|
// Minor page faults
|
|
MinorPageFaults uint64
|
|
|
|
// Major page faults
|
|
MajorPageFaults uint64
|
|
|
|
// IO input in kB
|
|
IOInputKB uint64
|
|
|
|
// IO output in kB
|
|
IOOutputKB uint64
|
|
|
|
// Voluntary context switches
|
|
VoluntaryContextSwitches uint64
|
|
|
|
// Involuntary context switches
|
|
InvoluntaryContextSwitches uint64
|
|
}
|
|
|
|
// Counts describes the number of actions in each state
|
|
type Counts struct {
|
|
// TotalActions is the total number of expected changes. This can
|
|
// generally change up or down during a build, but it should never go
|
|
// below the number of StartedActions
|
|
TotalActions int
|
|
|
|
// RunningActions are the number of actions that are currently running
|
|
// -- the number that have called StartAction, but not FinishAction.
|
|
RunningActions int
|
|
|
|
// StartedActions are the number of actions that have been started with
|
|
// StartAction.
|
|
StartedActions int
|
|
|
|
// FinishedActions are the number of actions that have been finished
|
|
// with FinishAction.
|
|
FinishedActions int
|
|
}
|
|
|
|
// ToolStatus is the interface used by tools to report on their Actions, and to
|
|
// present other information through a set of messaging functions.
|
|
type ToolStatus interface {
|
|
// SetTotalActions sets the expected total number of actions that will
|
|
// be started by this tool.
|
|
//
|
|
// This call be will ignored if it sets a number that is less than the
|
|
// current number of started actions.
|
|
SetTotalActions(total int)
|
|
|
|
// StartAction specifies that the associated action has been started by
|
|
// the tool.
|
|
//
|
|
// A specific *Action should not be specified to StartAction more than
|
|
// once, even if the previous action has already been finished, and the
|
|
// contents rewritten.
|
|
//
|
|
// Do not re-use *Actions between different ToolStatus interfaces
|
|
// either.
|
|
StartAction(action *Action)
|
|
|
|
// FinishAction specifies the result of a particular Action.
|
|
//
|
|
// The *Action embedded in the ActionResult structure must have already
|
|
// been passed to StartAction (on this interface).
|
|
//
|
|
// Do not call FinishAction twice for the same *Action.
|
|
FinishAction(result ActionResult)
|
|
|
|
// Verbose takes a non-important message that is never printed to the
|
|
// screen, but is in the verbose build log, etc
|
|
Verbose(msg string)
|
|
// Status takes a less important message that may be printed to the
|
|
// screen, but overwritten by another status message. The full message
|
|
// will still appear in the verbose build log.
|
|
Status(msg string)
|
|
// Print takes an message and displays it to the screen and other
|
|
// output logs, etc.
|
|
Print(msg string)
|
|
// Error is similar to Print, but treats it similarly to a failed
|
|
// action, showing it in the error logs, etc.
|
|
Error(msg string)
|
|
|
|
// Finish marks the end of all Actions being run by this tool.
|
|
//
|
|
// SetTotalEdges, StartAction, and FinishAction should not be called
|
|
// after Finish.
|
|
Finish()
|
|
}
|
|
|
|
// MsgLevel specifies the importance of a particular log message. See the
|
|
// descriptions in ToolStatus: Verbose, Status, Print, Error.
|
|
type MsgLevel int
|
|
|
|
const (
|
|
VerboseLvl MsgLevel = iota
|
|
StatusLvl
|
|
PrintLvl
|
|
ErrorLvl
|
|
)
|
|
|
|
func (l MsgLevel) Prefix() string {
|
|
switch l {
|
|
case VerboseLvl:
|
|
return "verbose: "
|
|
case StatusLvl:
|
|
return "status: "
|
|
case PrintLvl:
|
|
return ""
|
|
case ErrorLvl:
|
|
return "error: "
|
|
default:
|
|
panic("Unknown message level")
|
|
}
|
|
}
|
|
|
|
// StatusOutput is the interface used to get status information as a Status
|
|
// output.
|
|
//
|
|
// All of the functions here are guaranteed to be called by Status while
|
|
// holding it's internal lock, so it's safe to assume a single caller at any
|
|
// time, and that the ordering of calls will be correct. It is not safe to call
|
|
// back into the Status, or one of its ToolStatus interfaces.
|
|
type StatusOutput interface {
|
|
// StartAction will be called once every time ToolStatus.StartAction is
|
|
// called. counts will include the current counters across all
|
|
// ToolStatus instances, including ones that have been finished.
|
|
StartAction(action *Action, counts Counts)
|
|
|
|
// FinishAction will be called once every time ToolStatus.FinishAction
|
|
// is called. counts will include the current counters across all
|
|
// ToolStatus instances, including ones that have been finished.
|
|
FinishAction(result ActionResult, counts Counts)
|
|
|
|
// Message is the equivalent of ToolStatus.Verbose/Status/Print/Error,
|
|
// but the level is specified as an argument.
|
|
Message(level MsgLevel, msg string)
|
|
|
|
// Flush is called when your outputs should be flushed / closed. No
|
|
// output is expected after this call.
|
|
Flush()
|
|
|
|
// Write lets StatusOutput implement io.Writer
|
|
Write(p []byte) (n int, err error)
|
|
}
|
|
|
|
// Status is the multiplexer / accumulator between ToolStatus instances (via
|
|
// StartTool) and StatusOutputs (via AddOutput). There's generally one of these
|
|
// per build process (though tools like multiproduct_kati may have multiple
|
|
// independent versions).
|
|
type Status struct {
|
|
counts Counts
|
|
outputs []StatusOutput
|
|
|
|
// Protects counts and outputs, and allows each output to
|
|
// expect only a single caller at a time.
|
|
lock sync.Mutex
|
|
}
|
|
|
|
// AddOutput attaches an output to this object. It's generally expected that an
|
|
// output is attached to a single Status instance.
|
|
func (s *Status) AddOutput(output StatusOutput) {
|
|
if output == nil {
|
|
return
|
|
}
|
|
|
|
s.lock.Lock()
|
|
defer s.lock.Unlock()
|
|
|
|
s.outputs = append(s.outputs, output)
|
|
}
|
|
|
|
// StartTool returns a new ToolStatus instance to report the status of a tool.
|
|
func (s *Status) StartTool() ToolStatus {
|
|
return &toolStatus{
|
|
status: s,
|
|
}
|
|
}
|
|
|
|
// Finish will call Flush on all the outputs, generally flushing or closing all
|
|
// of their outputs. Do not call any other functions on this instance or any
|
|
// associated ToolStatus instances after this has been called.
|
|
func (s *Status) Finish() {
|
|
s.lock.Lock()
|
|
defer s.lock.Unlock()
|
|
|
|
for _, o := range s.outputs {
|
|
o.Flush()
|
|
}
|
|
}
|
|
|
|
func (s *Status) updateTotalActions(diff int) {
|
|
s.lock.Lock()
|
|
defer s.lock.Unlock()
|
|
|
|
s.counts.TotalActions += diff
|
|
}
|
|
|
|
func (s *Status) startAction(action *Action) {
|
|
s.lock.Lock()
|
|
defer s.lock.Unlock()
|
|
|
|
s.counts.RunningActions += 1
|
|
s.counts.StartedActions += 1
|
|
|
|
for _, o := range s.outputs {
|
|
o.StartAction(action, s.counts)
|
|
}
|
|
}
|
|
|
|
func (s *Status) finishAction(result ActionResult) {
|
|
s.lock.Lock()
|
|
defer s.lock.Unlock()
|
|
|
|
s.counts.RunningActions -= 1
|
|
s.counts.FinishedActions += 1
|
|
|
|
for _, o := range s.outputs {
|
|
o.FinishAction(result, s.counts)
|
|
}
|
|
}
|
|
|
|
func (s *Status) message(level MsgLevel, msg string) {
|
|
s.lock.Lock()
|
|
defer s.lock.Unlock()
|
|
|
|
for _, o := range s.outputs {
|
|
o.Message(level, msg)
|
|
}
|
|
}
|
|
|
|
func (s *Status) Status(msg string) {
|
|
s.message(StatusLvl, msg)
|
|
}
|
|
|
|
type toolStatus struct {
|
|
status *Status
|
|
|
|
counts Counts
|
|
// Protects counts
|
|
lock sync.Mutex
|
|
}
|
|
|
|
var _ ToolStatus = (*toolStatus)(nil)
|
|
|
|
func (d *toolStatus) SetTotalActions(total int) {
|
|
diff := 0
|
|
|
|
d.lock.Lock()
|
|
if total >= d.counts.StartedActions && total != d.counts.TotalActions {
|
|
diff = total - d.counts.TotalActions
|
|
d.counts.TotalActions = total
|
|
}
|
|
d.lock.Unlock()
|
|
|
|
if diff != 0 {
|
|
d.status.updateTotalActions(diff)
|
|
}
|
|
}
|
|
|
|
func (d *toolStatus) StartAction(action *Action) {
|
|
totalDiff := 0
|
|
|
|
d.lock.Lock()
|
|
d.counts.RunningActions += 1
|
|
d.counts.StartedActions += 1
|
|
|
|
if d.counts.StartedActions > d.counts.TotalActions {
|
|
totalDiff = d.counts.StartedActions - d.counts.TotalActions
|
|
d.counts.TotalActions = d.counts.StartedActions
|
|
}
|
|
d.lock.Unlock()
|
|
|
|
if totalDiff != 0 {
|
|
d.status.updateTotalActions(totalDiff)
|
|
}
|
|
d.status.startAction(action)
|
|
}
|
|
|
|
func (d *toolStatus) FinishAction(result ActionResult) {
|
|
d.lock.Lock()
|
|
d.counts.RunningActions -= 1
|
|
d.counts.FinishedActions += 1
|
|
d.lock.Unlock()
|
|
|
|
d.status.finishAction(result)
|
|
}
|
|
|
|
func (d *toolStatus) Verbose(msg string) {
|
|
d.status.message(VerboseLvl, msg)
|
|
}
|
|
func (d *toolStatus) Status(msg string) {
|
|
d.status.message(StatusLvl, msg)
|
|
}
|
|
func (d *toolStatus) Print(msg string) {
|
|
d.status.message(PrintLvl, msg)
|
|
}
|
|
func (d *toolStatus) Error(msg string) {
|
|
d.status.message(ErrorLvl, msg)
|
|
}
|
|
|
|
func (d *toolStatus) Finish() {
|
|
d.lock.Lock()
|
|
defer d.lock.Unlock()
|
|
|
|
if d.counts.TotalActions != d.counts.StartedActions {
|
|
d.status.updateTotalActions(d.counts.StartedActions - d.counts.TotalActions)
|
|
}
|
|
|
|
// TODO: update status to correct running/finished edges?
|
|
d.counts.RunningActions = 0
|
|
d.counts.TotalActions = d.counts.StartedActions
|
|
}
|