github.com/nya3jp/tast@v0.0.0-20230601000426-85c8e4d83a9b/src/go.chromium.org/tast/core/internal/timing/timing.go (about)

     1  // Copyright 2017 The ChromiumOS Authors
     2  // Use of this source code is governed by a BSD-style license that can be
     3  // found in the LICENSE file.
     4  
     5  // Package timing is used to collect and write timing information about a process.
     6  package timing
     7  
     8  import (
     9  	"bufio"
    10  	"encoding/json"
    11  	"fmt"
    12  	"io"
    13  	"strings"
    14  	"sync"
    15  	"time"
    16  
    17  	"github.com/golang/protobuf/ptypes"
    18  	"github.com/golang/protobuf/ptypes/timestamp"
    19  
    20  	"go.chromium.org/tast/core/errors"
    21  	"go.chromium.org/tast/core/internal/protocol"
    22  )
    23  
    24  // now is the function to return the current time. This is altered in unit tests.
    25  var now = time.Now
    26  
    27  // Log contains nested timing information.
    28  type Log struct {
    29  	// Root is a special root stage containing all stages as its descendants.
    30  	// Its End should not be called, and its timestamps should be ignored.
    31  	Root *Stage
    32  }
    33  
    34  // NewLog returns a new Log.
    35  func NewLog() *Log {
    36  	return &Log{Root: &Stage{}}
    37  }
    38  
    39  // StartTop starts and returns a new top-level stage named name.
    40  func (l *Log) StartTop(name string) *Stage {
    41  	return l.Root.StartChild(name)
    42  }
    43  
    44  // Empty returns true if l doesn't contain any stages.
    45  func (l *Log) Empty() bool {
    46  	l.Root.mu.Lock()
    47  	defer l.Root.mu.Unlock()
    48  
    49  	return len(l.Root.Children) == 0
    50  }
    51  
    52  // WritePretty writes timing information to w as JSON, consisting of an array
    53  // of stages, each represented by an array consisting of the stage's duration, name,
    54  // and an optional array of child stages.
    55  //
    56  // Note that this format is lossy and differs from that used by json.Marshaler.
    57  //
    58  // Output is intended to improve human readability:
    59  //
    60  //	[[4.000, "stage0", [
    61  //	         [3.000, "stage1", [
    62  //	                 [1.000, "stage2"],
    63  //	                 [2.000, "stage3"]]],
    64  //	         [1.000, "stage4"]]],
    65  //	 [0.531, "stage5"]]
    66  func (l *Log) WritePretty(w io.Writer) error {
    67  	l.Root.mu.Lock()
    68  	defer l.Root.mu.Unlock()
    69  
    70  	// Use a bufio.Writer to avoid any further writes after an error is encountered.
    71  	bw := bufio.NewWriter(w)
    72  
    73  	io.WriteString(bw, "[")
    74  	for i, s := range l.Root.Children {
    75  		// The first top-level stage is on the same line as the opening '['.
    76  		// Indent its children and all subsequent stages by a single space so they line up.
    77  		var indent string
    78  		if i > 0 {
    79  			indent = " "
    80  		}
    81  		if err := s.writePretty(bw, indent, " ", i == len(l.Root.Children)-1); err != nil {
    82  			return err
    83  		}
    84  	}
    85  
    86  	io.WriteString(bw, "]\n")
    87  	return bw.Flush() // returns first error encountered during earlier writes
    88  }
    89  
    90  // Proto returns a protobuf presentation of Log.
    91  func (l *Log) Proto() (*protocol.TimingLog, error) {
    92  	r, err := l.Root.Proto()
    93  	if err != nil {
    94  		return nil, err
    95  	}
    96  	return &protocol.TimingLog{Root: r}, nil
    97  }
    98  
    99  // jsonLog represents the JSON schema of Log.
   100  type jsonLog struct {
   101  	Stages []*Stage `json:"stages"`
   102  }
   103  
   104  // MarshalJSON marshals Log as JSON.
   105  func (l *Log) MarshalJSON() ([]byte, error) {
   106  	return json.Marshal(&jsonLog{Stages: l.Root.Children})
   107  }
   108  
   109  // UnmarshalJSON unmarshals Log as JSON.
   110  func (l *Log) UnmarshalJSON(b []byte) error {
   111  	var jl jsonLog
   112  	if err := json.Unmarshal(b, &jl); err != nil {
   113  		return err
   114  	}
   115  	l.Root = &Stage{Children: jl.Stages}
   116  	return nil
   117  }
   118  
   119  var _ json.Marshaler = (*Log)(nil)
   120  var _ json.Unmarshaler = (*Log)(nil)
   121  
   122  // Stage represents a discrete unit of work that is being timed.
   123  type Stage struct {
   124  	Name      string    `json:"name"`
   125  	StartTime time.Time `json:"startTime"`
   126  	EndTime   time.Time `json:"endTime"`
   127  	Children  []*Stage  `json:"children"`
   128  
   129  	mu sync.Mutex // protects EndTime and Children
   130  }
   131  
   132  // Import imports the stages from o into s, with o's top-level stages inserted as children of s.
   133  // An error is returned if s is already ended.
   134  func (s *Stage) Import(o *Log) error {
   135  	s.mu.Lock()
   136  	defer s.mu.Unlock()
   137  
   138  	if !s.EndTime.IsZero() {
   139  		return errors.New("stage has ended")
   140  	}
   141  
   142  	s.Children = append(s.Children, o.Root.Children...)
   143  	return nil
   144  }
   145  
   146  // StartChild creates and returns a new named timing stage as a child of s.
   147  // Stage.End should be called when the stage is completed.
   148  func (s *Stage) StartChild(name string) *Stage {
   149  	s.mu.Lock()
   150  	defer s.mu.Unlock()
   151  
   152  	if !s.EndTime.IsZero() {
   153  		return nil
   154  	}
   155  
   156  	c := &Stage{
   157  		Name:      name,
   158  		StartTime: now(),
   159  	}
   160  	s.Children = append(s.Children, c)
   161  	return c
   162  }
   163  
   164  // End ends the stage. Child stages are recursively examined and also ended
   165  // (although we expect them to have already been ended).
   166  func (s *Stage) End() {
   167  	// Handle nil receivers returned by the package-level Start function.
   168  	if s == nil {
   169  		return
   170  	}
   171  
   172  	s.mu.Lock()
   173  	defer s.mu.Unlock()
   174  
   175  	if !s.EndTime.IsZero() {
   176  		return
   177  	}
   178  
   179  	for _, c := range s.Children {
   180  		c.End()
   181  	}
   182  	s.EndTime = now()
   183  }
   184  
   185  // writePretty writes information about the stage and its children to w as a JSON array.
   186  // The first line of output is indented by initialIndent, while any subsequent lines (e.g.
   187  // for child stages) are indented by followIndent. last should be true if this is the last
   188  // entry in its parent array; otherwise a trailing comma and newline are appended.
   189  // The caller is responsible for checking w for errors encountered while writing.
   190  func (s *Stage) writePretty(w *bufio.Writer, initialIndent, followIndent string, last bool) error {
   191  	s.mu.Lock()
   192  	defer s.mu.Unlock()
   193  
   194  	// Start the stage's array.
   195  	mn, err := json.Marshal(&s.Name)
   196  	if err != nil {
   197  		return err
   198  	}
   199  
   200  	var elapsed time.Duration
   201  	if s.EndTime.IsZero() {
   202  		elapsed = now().Sub(s.StartTime)
   203  	} else {
   204  		elapsed = s.EndTime.Sub(s.StartTime)
   205  	}
   206  	fmt.Fprintf(w, "%s[%0.3f, %s", initialIndent, elapsed.Seconds(), mn)
   207  
   208  	// Print children in a nested array.
   209  	if len(s.Children) > 0 {
   210  		io.WriteString(w, ", [\n")
   211  		ci := followIndent + strings.Repeat(" ", 8)
   212  		for i, c := range s.Children {
   213  			if err := c.writePretty(w, ci, ci, i == len(s.Children)-1); err != nil {
   214  				return err
   215  			}
   216  		}
   217  		io.WriteString(w, "]")
   218  	}
   219  
   220  	// End the stage's array.
   221  	io.WriteString(w, "]")
   222  	if !last {
   223  		io.WriteString(w, ",\n")
   224  	}
   225  	return nil
   226  }
   227  
   228  // Proto returns a protobuf presentation of Stage.
   229  func (s *Stage) Proto() (*protocol.TimingStage, error) {
   230  	start, err := timestampProto(s.StartTime)
   231  	if err != nil {
   232  		return nil, errors.Wrap(err, "failed to convert StartTime to protobuf")
   233  	}
   234  	end, err := timestampProto(s.EndTime)
   235  	if err != nil {
   236  		return nil, errors.Wrap(err, "failed to convert EndTime to protobuf")
   237  	}
   238  	var children []*protocol.TimingStage
   239  	for _, c := range s.Children {
   240  		child, err := c.Proto()
   241  		if err != nil {
   242  			return nil, err
   243  		}
   244  		children = append(children, child)
   245  	}
   246  	return &protocol.TimingStage{
   247  		Name:      s.Name,
   248  		StartTime: start,
   249  		EndTime:   end,
   250  		Children:  children,
   251  	}, nil
   252  }
   253  
   254  func timestampProto(t time.Time) (*timestamp.Timestamp, error) {
   255  	if t.IsZero() {
   256  		return nil, nil
   257  	}
   258  	return ptypes.TimestampProto(t)
   259  }
   260  
   261  // LogFromProto constructs Log from its protocol buffer presentation.
   262  func LogFromProto(p *protocol.TimingLog) (*Log, error) {
   263  	s, err := StageFromProto(p.GetRoot())
   264  	if err != nil {
   265  		return nil, err
   266  	}
   267  	return &Log{Root: s}, nil
   268  }
   269  
   270  // StageFromProto constructs Stage from its protocol buffer presentation.
   271  func StageFromProto(p *protocol.TimingStage) (*Stage, error) {
   272  	var start, end time.Time
   273  	if ts := p.GetStartTime(); ts != nil {
   274  		var err error
   275  		start, err = ptypes.Timestamp(ts)
   276  		if err != nil {
   277  			return nil, err
   278  		}
   279  	}
   280  	if ts := p.GetEndTime(); ts != nil {
   281  		var err error
   282  		end, err = ptypes.Timestamp(ts)
   283  		if err != nil {
   284  			return nil, err
   285  		}
   286  	}
   287  	var children []*Stage
   288  	for _, c := range p.GetChildren() {
   289  		child, err := StageFromProto(c)
   290  		if err != nil {
   291  			return nil, err
   292  		}
   293  		children = append(children, child)
   294  	}
   295  	return &Stage{
   296  		Name:      p.GetName(),
   297  		StartTime: start,
   298  		EndTime:   end,
   299  		Children:  children,
   300  	}, nil
   301  }