github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/timings/timings_test.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_test
    21  
    22  import (
    23  	"fmt"
    24  	"testing"
    25  	"time"
    26  
    27  	. "gopkg.in/check.v1"
    28  
    29  	"github.com/snapcore/snapd/overlord/state"
    30  	"github.com/snapcore/snapd/testutil"
    31  	"github.com/snapcore/snapd/timings"
    32  )
    33  
    34  func TestTimings(t *testing.T) { TestingT(t) }
    35  
    36  type timingsSuite struct {
    37  	testutil.BaseTest
    38  	st       *state.State
    39  	duration time.Duration
    40  	fakeTime time.Time
    41  }
    42  
    43  var _ = Suite(&timingsSuite{})
    44  
    45  func (s *timingsSuite) SetUpTest(c *C) {
    46  	s.BaseTest.SetUpTest(c)
    47  
    48  	s.st = state.New(nil)
    49  	s.duration = 0
    50  
    51  	s.mockTimeNow(c)
    52  	s.mockDurationThreshold(0)
    53  }
    54  
    55  func (s *timingsSuite) TearDownTest(c *C) {
    56  	s.BaseTest.TearDownTest(c)
    57  }
    58  
    59  func (s *timingsSuite) mockDuration(c *C) {
    60  	// Increase duration by 1 millisecond on each call
    61  	s.BaseTest.AddCleanup(timings.MockTimeDuration(func(start, end time.Time) time.Duration {
    62  		c.Check(start.Before(end), Equals, true)
    63  		s.duration += time.Millisecond
    64  		return s.duration
    65  	}))
    66  }
    67  
    68  func (s *timingsSuite) mockDurationThreshold(threshold time.Duration) {
    69  	oldDurationThreshold := timings.DurationThreshold
    70  	timings.DurationThreshold = threshold
    71  	restore := func() {
    72  		timings.DurationThreshold = oldDurationThreshold
    73  	}
    74  	s.AddCleanup(restore)
    75  }
    76  
    77  func (s *timingsSuite) mockTimeNow(c *C) {
    78  	t, err := time.Parse(time.RFC3339, "2019-03-11T09:01:00.0Z")
    79  	c.Assert(err, IsNil)
    80  	s.fakeTime = t
    81  	// Increase fakeTime by 1 millisecond on each call, and report it as current time
    82  	s.BaseTest.AddCleanup(timings.MockTimeNow(func() time.Time {
    83  		s.fakeTime = s.fakeTime.Add(time.Millisecond)
    84  		return s.fakeTime
    85  	}))
    86  }
    87  
    88  func (s *timingsSuite) TestSave(c *C) {
    89  	s.mockDuration(c)
    90  
    91  	s.st.Lock()
    92  	defer s.st.Unlock()
    93  
    94  	// two timings, with 2 nested measures
    95  	for i := 0; i < 2; i++ {
    96  		timing := timings.New(map[string]string{"task": "3"})
    97  		timing.AddTag("change", "12")
    98  		meas := timing.StartSpan(fmt.Sprintf("doing something-%d", i), "...")
    99  		nested := meas.StartSpan("nested measurement", "...")
   100  		var called bool
   101  		timings.Run(nested, "nested more", "...", func(span timings.Measurer) {
   102  			called = true
   103  		})
   104  		c.Check(called, Equals, true)
   105  		nested.Stop()
   106  		meas.Stop()
   107  		timing.Save(s.st)
   108  	}
   109  
   110  	var stateTimings []interface{}
   111  	c.Assert(s.st.Get("timings", &stateTimings), IsNil)
   112  
   113  	c.Assert(stateTimings, DeepEquals, []interface{}{
   114  		map[string]interface{}{
   115  			"tags":       map[string]interface{}{"change": "12", "task": "3"},
   116  			"start-time": "2019-03-11T09:01:00.001Z",
   117  			"stop-time":  "2019-03-11T09:01:00.006Z",
   118  			"timings": []interface{}{
   119  				map[string]interface{}{
   120  					"label":    "doing something-0",
   121  					"summary":  "...",
   122  					"duration": float64(1000000),
   123  				},
   124  				map[string]interface{}{
   125  					"level":    float64(1),
   126  					"label":    "nested measurement",
   127  					"summary":  "...",
   128  					"duration": float64(2000000)},
   129  				map[string]interface{}{
   130  					"level":    float64(2),
   131  					"label":    "nested more",
   132  					"summary":  "...",
   133  					"duration": float64(3000000)},
   134  			}},
   135  		map[string]interface{}{
   136  			"tags":       map[string]interface{}{"change": "12", "task": "3"},
   137  			"start-time": "2019-03-11T09:01:00.007Z",
   138  			"stop-time":  "2019-03-11T09:01:00.012Z",
   139  			"timings": []interface{}{
   140  				map[string]interface{}{
   141  					"label":    "doing something-1",
   142  					"summary":  "...",
   143  					"duration": float64(4000000),
   144  				},
   145  				map[string]interface{}{
   146  					"level":    float64(1),
   147  					"label":    "nested measurement",
   148  					"summary":  "...",
   149  					"duration": float64(5000000)},
   150  				map[string]interface{}{
   151  					"level":    float64(2),
   152  					"label":    "nested more",
   153  					"summary":  "...",
   154  					"duration": float64(6000000)},
   155  			}}})
   156  }
   157  
   158  func (s *timingsSuite) TestSaveNoTimings(c *C) {
   159  	s.mockDuration(c)
   160  
   161  	s.st.Lock()
   162  	defer s.st.Unlock()
   163  
   164  	timing := timings.New(nil)
   165  	timing.Save(s.st)
   166  
   167  	var stateTimings []interface{}
   168  	c.Assert(s.st.Get("timings", &stateTimings), Equals, state.ErrNoState)
   169  }
   170  
   171  func (s *timingsSuite) TestDuration(c *C) {
   172  	s.st.Lock()
   173  	defer s.st.Unlock()
   174  
   175  	timing := timings.New(nil)
   176  	meas := timing.StartSpan("foo", "...")                   // time now = 1
   177  	nested := meas.StartSpan("nested", "...")                // time now = 2
   178  	nested.Stop()                                            // time now = 3 -> duration = 1
   179  	nestedSibling := meas.StartSpan("nested sibling", "...") // time now = 4
   180  	nestedSibling.Stop()                                     // time now = 5 -> duration = 1
   181  	meas.Stop()
   182  	timing.Save(s.st)
   183  
   184  	var stateTimings []interface{}
   185  	c.Assert(s.st.Get("timings", &stateTimings), IsNil)
   186  
   187  	c.Assert(stateTimings, DeepEquals, []interface{}{
   188  		map[string]interface{}{
   189  			"start-time": "2019-03-11T09:01:00.001Z",
   190  			"stop-time":  "2019-03-11T09:01:00.006Z",
   191  			"timings": []interface{}{
   192  				map[string]interface{}{
   193  					"label":    "foo",
   194  					"summary":  "...",
   195  					"duration": float64(5000000),
   196  				},
   197  				map[string]interface{}{
   198  					"level":    float64(1),
   199  					"label":    "nested",
   200  					"summary":  "...",
   201  					"duration": float64(1000000),
   202  				},
   203  				map[string]interface{}{
   204  					"level":    float64(1),
   205  					"label":    "nested sibling",
   206  					"summary":  "...",
   207  					"duration": float64(1000000),
   208  				},
   209  			}}})
   210  }
   211  
   212  func (s *timingsSuite) testDurationThreshold(c *C, threshold time.Duration, expected interface{}) {
   213  	s.mockDurationThreshold(threshold)
   214  
   215  	s.st.Lock()
   216  	defer s.st.Unlock()
   217  
   218  	timing := timings.New(nil)
   219  	meas := timing.StartSpan("main", "...")
   220  	nested := meas.StartSpan("nested", "...")
   221  	nestedMore := nested.StartSpan("nested more", "...")
   222  	nestedMore.Stop()
   223  	nested.Stop()
   224  
   225  	meas.Stop()
   226  	timing.Save(s.st)
   227  
   228  	var stateTimings []interface{}
   229  	if expected == nil {
   230  		c.Assert(s.st.Get("timings", &stateTimings), Equals, state.ErrNoState)
   231  		c.Assert(stateTimings, IsNil)
   232  		return
   233  	}
   234  	c.Assert(s.st.Get("timings", &stateTimings), IsNil)
   235  	c.Check(stateTimings, DeepEquals, expected)
   236  }
   237  
   238  func (s *timingsSuite) TestDurationThresholdAll(c *C) {
   239  	s.testDurationThreshold(c, 0, []interface{}{
   240  		map[string]interface{}{
   241  			"start-time": "2019-03-11T09:01:00.001Z",
   242  			"stop-time":  "2019-03-11T09:01:00.006Z",
   243  			"timings": []interface{}{
   244  				map[string]interface{}{
   245  					"label":    "main",
   246  					"summary":  "...",
   247  					"duration": float64(5000000),
   248  				},
   249  				map[string]interface{}{
   250  					"level":    float64(1),
   251  					"label":    "nested",
   252  					"summary":  "...",
   253  					"duration": float64(3000000),
   254  				},
   255  				map[string]interface{}{
   256  					"level":    float64(2),
   257  					"label":    "nested more",
   258  					"summary":  "...",
   259  					"duration": float64(1000000),
   260  				},
   261  			}}})
   262  }
   263  
   264  func (s *timingsSuite) TestDurationThreshold(c *C) {
   265  	s.testDurationThreshold(c, 3000000, []interface{}{
   266  		map[string]interface{}{
   267  			"start-time": "2019-03-11T09:01:00.001Z",
   268  			"stop-time":  "2019-03-11T09:01:00.006Z",
   269  			"timings": []interface{}{
   270  				map[string]interface{}{
   271  					"label":    "main",
   272  					"summary":  "...",
   273  					"duration": float64(5000000),
   274  				},
   275  				map[string]interface{}{
   276  					"level":    float64(1),
   277  					"label":    "nested",
   278  					"summary":  "...",
   279  					"duration": float64(3000000),
   280  				},
   281  			}}})
   282  }
   283  
   284  func (s *timingsSuite) TestDurationThresholdRootOnly(c *C) {
   285  	s.testDurationThreshold(c, 4000000, []interface{}{
   286  		map[string]interface{}{
   287  			"start-time": "2019-03-11T09:01:00.001Z",
   288  			"stop-time":  "2019-03-11T09:01:00.006Z",
   289  			"timings": []interface{}{
   290  				map[string]interface{}{
   291  					"label":    "main",
   292  					"summary":  "...",
   293  					"duration": float64(5000000),
   294  				},
   295  			}}})
   296  }
   297  
   298  func (s *timingsSuite) TestDurationThresholdNone(c *C) {
   299  	s.testDurationThreshold(c, time.Hour, nil)
   300  }
   301  
   302  func (s *timingsSuite) TestPurgeOnSave(c *C) {
   303  	oldMaxTimings := timings.MaxTimings
   304  	timings.MaxTimings = 3
   305  	defer func() {
   306  		timings.MaxTimings = oldMaxTimings
   307  	}()
   308  
   309  	s.st.Lock()
   310  	defer s.st.Unlock()
   311  
   312  	// Create lots of timings
   313  	for i := 0; i < 10; i++ {
   314  		t := timings.New(map[string]string{"number": fmt.Sprintf("%d", i)})
   315  		m := t.StartSpan("...", "...")
   316  		m.Stop()
   317  		t.Save(s.st)
   318  	}
   319  
   320  	var stateTimings []interface{}
   321  	c.Assert(s.st.Get("timings", &stateTimings), IsNil)
   322  
   323  	// excess timings got dropped
   324  	c.Assert(stateTimings, HasLen, 3)
   325  	c.Check(stateTimings[0].(map[string]interface{})["tags"], DeepEquals, map[string]interface{}{"number": "7"})
   326  	c.Check(stateTimings[1].(map[string]interface{})["tags"], DeepEquals, map[string]interface{}{"number": "8"})
   327  	c.Check(stateTimings[2].(map[string]interface{})["tags"], DeepEquals, map[string]interface{}{"number": "9"})
   328  }
   329  
   330  func (s *timingsSuite) TestGet(c *C) {
   331  	s.st.Lock()
   332  	defer s.st.Unlock()
   333  
   334  	// three timings, with 2 nested measures
   335  	for i := 0; i < 3; i++ {
   336  		timing := timings.New(map[string]string{"foo": fmt.Sprintf("%d", i)})
   337  		meas := timing.StartSpan(fmt.Sprintf("doing something-%d", i), "...")
   338  		nested := meas.StartSpan("nested measurement", "...")
   339  		nested.Stop()
   340  		meas.Stop()
   341  		timing.Save(s.st)
   342  	}
   343  
   344  	none, err := timings.Get(s.st, 999, func(tags map[string]string) bool { return false })
   345  	c.Assert(err, IsNil)
   346  	c.Check(none, HasLen, 0)
   347  
   348  	tm, err := timings.Get(s.st, -1, func(tags map[string]string) bool {
   349  		return tags["foo"] == "1"
   350  	})
   351  	c.Assert(err, IsNil)
   352  	c.Check(tm, DeepEquals, []*timings.TimingsInfo{
   353  		{
   354  			Tags:     map[string]string{"foo": "1"},
   355  			Duration: 3000000,
   356  			NestedTimings: []*timings.TimingJSON{
   357  				{Level: 0, Label: "doing something-1", Summary: "...", Duration: 3000000},
   358  				{Level: 1, Label: "nested measurement", Summary: "...", Duration: 1000000},
   359  			},
   360  		},
   361  	})
   362  
   363  	tmOnlyLevel0, err := timings.Get(s.st, 0, func(tags map[string]string) bool { return true })
   364  	c.Assert(err, IsNil)
   365  	c.Check(tmOnlyLevel0, DeepEquals, []*timings.TimingsInfo{
   366  		{
   367  			Tags:     map[string]string{"foo": "0"},
   368  			Duration: 3000000,
   369  			NestedTimings: []*timings.TimingJSON{
   370  				{Level: 0, Label: "doing something-0", Summary: "...", Duration: 3000000},
   371  			},
   372  		},
   373  		{
   374  			Tags:     map[string]string{"foo": "1"},
   375  			Duration: 3000000,
   376  			NestedTimings: []*timings.TimingJSON{
   377  				{Level: 0, Label: "doing something-1", Summary: "...", Duration: 3000000},
   378  			},
   379  		},
   380  		{
   381  			Tags:     map[string]string{"foo": "2"},
   382  			Duration: 3000000,
   383  			NestedTimings: []*timings.TimingJSON{
   384  				{Level: 0, Label: "doing something-2", Summary: "...", Duration: 3000000},
   385  			},
   386  		},
   387  	})
   388  }