github.com/tompreston/snapd@v0.0.0-20210817193607-954edfcb9611/timings/timings.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2019 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package timings
    21  
    22  import (
    23  	"time"
    24  )
    25  
    26  var timeNow = func() time.Time {
    27  	return time.Now()
    28  }
    29  
    30  // Timings represents a tree of Span time measurements for a single execution of measured activity.
    31  // A Timings tree object should be created at the beginning of the activity,
    32  // followed by starting at least one Span, and then saved at the end of the activity.
    33  //
    34  // Calling StartSpan on the Timings objects creates a Span and starts new
    35  // performance measurement. Measurement needs to be finished by calling Stop
    36  // function on the Span object.
    37  // Nested measurements may be collected by calling StartSpan on Span objects. Similar
    38  // to the above, nested measurements need to be finished by calling Stop on them.
    39  //
    40  // Typical usage:
    41  //   troot := timings.New(map[string]string{"task-id": task.ID(), "change-id": task.Change().ID()})
    42  //   t1 := troot.StartSpan("computation", "...")
    43  //   ....
    44  //   nestedTiming := t1.StartSpan("sub-computation", "...")
    45  //   ....
    46  //   nestedTiming.Stop()
    47  //   t1.Stop()
    48  //   troot.Save()
    49  //
    50  // In addition, a few helpers exist to simplify typical use cases, for example the above example
    51  // can be reduced to:
    52  //   troot := state.TimingsForTask(task) // tags set automatically from task
    53  //   t1 := troot.StartSpan("computation", "...")
    54  //   timings.Run(t1, "sub-computation", "...", func(nested *Span) {
    55  //          ... expensive computation
    56  //   })
    57  //   t1.Stop()
    58  //   troot.Save(task.State())
    59  type Timings struct {
    60  	tags    map[string]string
    61  	timings []*Span
    62  }
    63  
    64  // Span represents a single performance measurement with optional nested measurements.
    65  type Span struct {
    66  	label, summary string
    67  	start, stop    time.Time
    68  	timings        []*Span
    69  }
    70  
    71  type Measurer interface {
    72  	StartSpan(label, summary string) *Span
    73  }
    74  
    75  // New creates a Timings object. Tags provide extra information (such as "task-id" and "change-id")
    76  // that can be used by the client when retrieving timings.
    77  func New(tags map[string]string) *Timings {
    78  	return &Timings{
    79  		tags: tags,
    80  	}
    81  }
    82  
    83  // AddTag sets a tag on the Timings object.
    84  func (t *Timings) AddTag(tag, value string) {
    85  	if t.tags == nil {
    86  		t.tags = make(map[string]string)
    87  	}
    88  	t.tags[tag] = value
    89  }
    90  
    91  func startSpan(label, summary string) *Span {
    92  	tmeas := &Span{
    93  		label:   label,
    94  		summary: summary,
    95  		start:   timeNow(),
    96  	}
    97  	return tmeas
    98  }
    99  
   100  // StartSpan creates a Span and initiates performance measurement.
   101  // Measurement needs to be stopped by calling Stop on it.
   102  func (t *Timings) StartSpan(label, summary string) *Span {
   103  	tmeas := startSpan(label, summary)
   104  	t.timings = append(t.timings, tmeas)
   105  	return tmeas
   106  }
   107  
   108  // StartSpan creates a new nested Span and initiates performance measurement.
   109  // Nested measurements need to be stopped by calling Stop on it.
   110  func (t *Span) StartSpan(label, summary string) *Span {
   111  	tmeas := startSpan(label, summary)
   112  	t.timings = append(t.timings, tmeas)
   113  	return tmeas
   114  }
   115  
   116  // Stop stops the measurement.
   117  func (t *Span) Stop() {
   118  	if t.stop.IsZero() {
   119  		t.stop = timeNow()
   120  	} // else - stopping already stopped timing is an error, but just ignore it
   121  }