From d904a4c872dba2e8f549d9bf31b32e6d1f964ae1 Mon Sep 17 00:00:00 2001 From: dlorenc Date: Fri, 28 Sep 2018 09:13:17 -0700 Subject: [PATCH] Add a benchmark package to store and monitor timings. (#367) --- pkg/timing/timing.go | 90 ++++++++++++++++++++++++ pkg/timing/timing_test.go | 139 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 229 insertions(+) create mode 100644 pkg/timing/timing.go create mode 100644 pkg/timing/timing_test.go diff --git a/pkg/timing/timing.go b/pkg/timing/timing.go new file mode 100644 index 0000000000..fcd2127682 --- /dev/null +++ b/pkg/timing/timing.go @@ -0,0 +1,90 @@ +/* +Copyright 2018 Google LLC + +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 timing + +import ( + "bytes" + "fmt" + "sync" + "text/template" + "time" +) + +// For testing +var currentTimeFunc = time.Now + +// DefaultRun is the default "singleton" TimedRun instance. +var DefaultRun = NewTimedRun() + +// TimedRun provides a running store of how long is spent in each category. +type TimedRun struct { + cl sync.Mutex + categories map[string]time.Duration // protected by cl +} + +// Stop stops the specified timer and increments the time spent in that category. +func (tr *TimedRun) Stop(t *Timer) { + stop := currentTimeFunc() + if _, ok := tr.categories[t.category]; !ok { + tr.categories[t.category] = 0 + } + fmt.Println(stop) + tr.cl.Lock() + defer tr.cl.Unlock() + tr.categories[t.category] += stop.Sub(t.startTime) +} + +// Start starts a new Timer and returns it. +func Start(category string) *Timer { + t := Timer{ + category: category, + startTime: currentTimeFunc(), + } + return &t +} + +// NewTimedRun returns an initialized TimedRun instance. +func NewTimedRun() *TimedRun { + tr := TimedRun{ + categories: map[string]time.Duration{}, + } + return &tr +} + +// Timer represents a running timer. +type Timer struct { + category string + startTime time.Time +} + +// DefaultFormat is a default format string used by Summary. +var DefaultFormat = template.Must(template.New("").Parse("{{range $c, $t := .}}{{$c}}: {{$t}}\n{{end}}")) + +// Summary outputs a summary of the DefaultTimedRun. +func Summary() string { + return DefaultRun.Summary() +} + +// Summary outputs a summary of the specified TimedRun. +func (tr *TimedRun) Summary() string { + b := bytes.Buffer{} + + tr.cl.Lock() + defer tr.cl.Unlock() + DefaultFormat.Execute(&b, tr.categories) + return b.String() +} diff --git a/pkg/timing/timing_test.go b/pkg/timing/timing_test.go new file mode 100644 index 0000000000..43082c191f --- /dev/null +++ b/pkg/timing/timing_test.go @@ -0,0 +1,139 @@ +/* +Copyright 2018 Google LLC + +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 timing + +import ( + "testing" + "time" +) + +func patchTime(timeFunc func() time.Time) func() { + old := currentTimeFunc + currentTimeFunc = timeFunc + return func() { + currentTimeFunc = old + } +} + +func mockTimeFunc(t time.Time) func() time.Time { + return func() time.Time { + return t + } +} + +func TestTimedRun_StartStop(t *testing.T) { + type args struct { + categories map[string]time.Duration + category string + waitTime time.Duration + } + tests := []struct { + name string + args args + want time.Duration + }{ + { + name: "new category", + args: args{ + categories: map[string]time.Duration{}, + category: "foo", + waitTime: 3 * time.Second, + }, + want: 3 * time.Second, + }, + { + name: "existing category", + args: args{ + categories: map[string]time.Duration{ + "foo": 4 * time.Second, + }, + category: "foo", + waitTime: 2 * time.Second, + }, + want: 6 * time.Second, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tr := &TimedRun{ + categories: tt.args.categories, + } + + timer := Timer{ + category: tt.args.category, + startTime: time.Time{}, + } + + defer patchTime(mockTimeFunc(timer.startTime.Add(tt.args.waitTime)))() + tr.Stop(&timer) + if got := tr.categories[tt.args.category]; got != tt.want { + t.Errorf("Expected %d, got %d", tt.want, got) + } + }) + } +} + +func TestTimedRun_Summary(t *testing.T) { + type fields struct { + categories map[string]time.Duration + } + tests := []struct { + name string + fields fields + want string + }{ + { + name: "single key", + fields: fields{ + categories: map[string]time.Duration{ + "foo": 3 * time.Second, + }, + }, + want: "foo: 3s\n", + }, + { + name: "two keys", + fields: fields{ + categories: map[string]time.Duration{ + "foo": 3 * time.Second, + "bar": 1 * time.Second, + }, + }, + want: "bar: 1s\nfoo: 3s\n", + }, + { + name: "units", + fields: fields{ + categories: map[string]time.Duration{ + "foo": 3 * time.Second, + "bar": 1 * time.Millisecond, + }, + }, + want: "bar: 1ms\nfoo: 3s\n", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tr := &TimedRun{ + categories: tt.fields.categories, + } + if got := tr.Summary(); got != tt.want { + t.Errorf("TimedRun.Summary() = %v, want %v", got, tt.want) + } + }) + } +}