mirror of https://github.com/knative/pkg.git
297 lines
8.4 KiB
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)
|
|
}
|
|
})
|
|
}
|
|
}
|