mirror of https://github.com/knative/pkg.git
				
				
				
			
		
			
				
	
	
		
			296 lines
		
	
	
		
			8.3 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			296 lines
		
	
	
		
			8.3 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 (
 | |
| 	"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.PollImmediate(100*time.Millisecond, 5*time.Second, func() (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.PollImmediate(100*time.Millisecond, 5*time.Second, func() (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)
 | |
| 			}
 | |
| 		})
 | |
| 	}
 | |
| }
 |