github.com/tompreston/snapd@v0.0.0-20210817193607-954edfcb9611/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 }