pkg/metrics/resource_view_test.go

297 lines
8.4 KiB
Go

/*
Copyright 2020 The Knative Authors
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 metrics
import (
"context"
"fmt"
"testing"
"time"
"go.opencensus.io/resource"
"go.opencensus.io/stats"
"go.opencensus.io/stats/view"
"k8s.io/apimachinery/pkg/util/wait"
testingclock "k8s.io/utils/clock/testing"
)
var (
r = resource.Resource{Labels: map[string]string{"foo": "bar"}}
)
func TestRegisterResourceView(t *testing.T) {
meter := meterExporterForResource(&r).m
m := stats.Int64("testView_sum", "", stats.UnitDimensionless)
view := view.View{Name: "testView", Measure: m, Aggregation: view.Sum()}
err := RegisterResourceView(&view)
if err != nil {
t.Fatal("RegisterResourceView =", err)
}
t.Cleanup(func() { UnregisterResourceView(&view) })
viewToFind := defaultMeter.m.Find("testView")
if viewToFind == nil || viewToFind.Name != "testView" {
t.Error("Registered view should be found in default meter, instead got", viewToFind)
}
viewToFind = meter.Find("testView")
if viewToFind == nil || viewToFind.Name != "testView" {
t.Error("Registered view should be found in new meter, instead got", viewToFind)
}
}
func TestOptionForResource(t *testing.T) {
option, err1 := optionForResource(&r)
if err1 != nil {
t.Error("Should succeed getting option, instead got error", err1)
}
optionAgain, err2 := optionForResource(&r)
if err2 != nil {
t.Error("Should succeed getting option, instead got error", err2)
}
if fmt.Sprintf("%v", optionAgain) != fmt.Sprintf("%v", option) {
t.Errorf("Option for the same resource should not be recreated, instead got %v and %v", optionAgain, option)
}
}
type testExporter struct {
view.Exporter
id string
}
func (testExporter) ExportView(viewData *view.Data) {}
func TestSetFactory(t *testing.T) {
var oldFactory ResourceExporterFactory
func() {
allMeters.lock.Lock()
defer allMeters.lock.Unlock()
oldFactory = allMeters.factory
}()
fakeFactory := func(rr *resource.Resource) (view.Exporter, error) {
if rr == nil {
return &testExporter{}, nil
}
return &testExporter{id: rr.Labels["id"]}, nil
}
resource123 := r
resource123.Labels["id"] = "123"
setFactory(fakeFactory)
// Create the new meter and apply the factory
_, err := optionForResource(&resource123)
if err != nil {
t.Error("Should succeed getting option, instead got error", err)
}
// Now get the exporter and verify the id
me := meterExporterForResource(&resource123)
e := me.e.(*testExporter)
if e.id != "123" {
t.Error("Expect id to be 123, instead got", e.id)
}
resource456 := r
resource456.Labels["id"] = "456"
// Create the new meter and apply the factory
_, err = optionForResource(&resource456)
if err != nil {
t.Error("Should succeed getting option, instead got error", err)
}
me = meterExporterForResource(&resource456)
e = me.e.(*testExporter)
if e.id != "456" {
t.Error("Expect id to be 456, instead got", e.id)
}
setFactory(oldFactory)
}
func TestAllMetersExpiration(t *testing.T) {
fakeClock := testingclock.NewFakeClock(time.Now())
allMeters.clock = fakeClock
ClearMetersForTest() // t+0m
// Add resource123
resource123 := r
resource123.Labels["id"] = "123"
_, err := optionForResource(&resource123)
if err != nil {
t.Error("Should succeed getting option, instead got error ", err)
}
// (123=0m, 456=Inf)
// Bump time to make resource123's expiry offset from resource456
fakeClock.Step(90 * time.Second) // t+1.5m
// (123=0m, 456=Inf)
// Add 456
resource456 := r
resource456.Labels["id"] = "456"
_, err = optionForResource(&resource456)
if err != nil {
t.Error("Should succeed getting option, instead got error ", err)
}
allMeters.lock.Lock()
if len(allMeters.meters) != 3 {
t.Errorf("len(allMeters)=%d, want: 3", len(allMeters.meters))
}
allMeters.lock.Unlock()
// (123=1.5m, 456=0m)
// Warm up the older entry
fakeClock.Step(90 * time.Second) //t+3m
// (123=4.5m, 456=3m)
// Refresh the first entry
_, err = optionForResource(&resource123)
if err != nil {
t.Error("Should succeed getting option, instead got error ", err)
}
// (123=0, 456=1.5m)
// Expire the second entry
fakeClock.Step(9 * time.Minute) // t+12m
_ = wait.PollUntilContextTimeout(context.Background(), 100*time.Millisecond, 5*time.Second, true, func(ctx context.Context) (bool, error) {
// Non-expiring defaultMeter should be available along with the non-expired entry
allMeters.lock.Lock()
defer allMeters.lock.Unlock()
return len(allMeters.meters) == 2, nil
})
allMeters.lock.Lock()
if len(allMeters.meters) != 2 {
t.Errorf("len(allMeters)=%d, want: 2", len(allMeters.meters))
}
allMeters.lock.Unlock()
// (123=9m, 456=10.5m)
// non-expiring defaultMeter was just tested
// Add resource789
resource789 := r
resource789.Labels["id"] = "789"
_, err = optionForResource(&resource789)
if err != nil {
t.Error("Should succeed getting option, instead got error ", err)
}
// (123=9m, 456=evicted, 789=0m)
}
func TestIfAllMeterResourcesAreRemoved(t *testing.T) {
fakeClock := testingclock.NewFakeClock(time.Now())
allMeters.clock = fakeClock
ClearMetersForTest() // t+0m
// Register many resources at once
for i := 1; i <= 100; i++ {
res := resource.Resource{Labels: map[string]string{"foo": "bar"}}
res.Labels["id"] = fmt.Sprint(i)
if _, err := optionForResource(&res); err != nil {
t.Error("Should succeed getting option, instead got error ", err)
}
m := stats.Int64("testView_sum", "", stats.UnitDimensionless)
v := view.View{Name: fmt.Sprintf("testview-%d", i), Measure: m, Aggregation: view.Sum()}
err := RegisterResourceView(&v)
if err != nil {
t.Fatal("RegisterResourceView =", err)
}
}
func() {
allMeters.lock.Lock()
resourceViews.lock.Lock()
defer allMeters.lock.Unlock()
defer resourceViews.lock.Unlock()
// Make a copy to test against as allMeters should be cleaned up
copyAllMeters := make(map[string]*meterExporter, len(allMeters.meters))
for k, v := range allMeters.meters {
copyAllMeters[k] = v
}
for _, meter := range copyAllMeters {
for _, mView := range resourceViews.views {
want, got := mView, meter.m.Find(mView.Name)
if got == nil {
t.Errorf("View %s is not available", want.Name)
} else if want.Name != got.Name {
t.Errorf("Want %v, got %v", want.Name, got.Name)
}
}
}
}()
// Expire all meters and views
// We need to unlock before we move the clock ahead in time
fakeClock.Step(12 * time.Minute) // t+12m
_ = wait.PollUntilContextTimeout(context.Background(), 100*time.Millisecond, 5*time.Second, true, func(ctx context.Context) (bool, error) {
// Non-expiring defaultMeter should be available
allMeters.lock.Lock()
defer allMeters.lock.Unlock()
return len(allMeters.meters) == 1, nil
})
allMeters.lock.Lock()
defer allMeters.lock.Unlock()
resourceViews.lock.Lock()
defer resourceViews.lock.Unlock()
if len(allMeters.meters) != 1 {
t.Errorf("len(allMeters)=%d, want: 1", len(allMeters.meters))
}
}
func TestResourceAsString(t *testing.T) {
r1 := &resource.Resource{Type: "foobar", Labels: map[string]string{"k1": "v1", "k3": "v3", "k2": "v2"}}
r2 := &resource.Resource{Type: "foobar", Labels: map[string]string{"k2": "v2", "k3": "v3", "k1": "v1"}}
r3 := &resource.Resource{Type: "foobar", Labels: map[string]string{"k1": "v1", "k2": "v2", "k4": "v4"}}
// Test 5 time since the iteration could be random.
for i := 0; i < 5; i++ {
if s1, s2 := resourceToKey(r1), resourceToKey(r2); s1 != s2 {
t.Errorf("Expect same resources, but got %q and %q", s1, s2)
}
}
if s1, s3 := resourceToKey(r1), resourceToKey(r3); s1 == s3 {
t.Error("Expect different resources, but got the same", s1)
}
}
func BenchmarkResourceToKey(b *testing.B) {
for _, count := range []int{0, 1, 5, 10} {
labels := make(map[string]string, count)
for i := 0; i < count; i++ {
labels[fmt.Sprint("key", i)] = fmt.Sprint("value", i)
}
r := &resource.Resource{Type: "foobar", Labels: labels}
b.Run(fmt.Sprintf("%d-labels", count), func(b *testing.B) {
for i := 0; i < b.N; i++ {
resourceToKey(r)
}
})
}
}