diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 22613e0b4483e..b956efc77f263 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -109,6 +109,10 @@ "ImportPath": "github.com/golang/glog", "Rev": "44145f04b68cf362d9c4df2182967c2275eaefed" }, + { + "ImportPath": "github.com/golang/groupcache/lru", + "Rev": "604ed5785183e59ae2789449d89e73f3a2a77987" + }, { "ImportPath": "github.com/golang/protobuf/proto", "Rev": "7f07925444bb51fa4cf9dfe6f7661876f8852275" diff --git a/Godeps/_workspace/src/github.com/golang/groupcache/lru/lru.go b/Godeps/_workspace/src/github.com/golang/groupcache/lru/lru.go new file mode 100644 index 0000000000000..cdfe2991fd40a --- /dev/null +++ b/Godeps/_workspace/src/github.com/golang/groupcache/lru/lru.go @@ -0,0 +1,121 @@ +/* +Copyright 2013 Google Inc. + +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 lru implements an LRU cache. +package lru + +import "container/list" + +// Cache is an LRU cache. It is not safe for concurrent access. +type Cache struct { + // MaxEntries is the maximum number of cache entries before + // an item is evicted. Zero means no limit. + MaxEntries int + + // OnEvicted optionally specificies a callback function to be + // executed when an entry is purged from the cache. + OnEvicted func(key Key, value interface{}) + + ll *list.List + cache map[interface{}]*list.Element +} + +// A Key may be any value that is comparable. See http://golang.org/ref/spec#Comparison_operators +type Key interface{} + +type entry struct { + key Key + value interface{} +} + +// New creates a new Cache. +// If maxEntries is zero, the cache has no limit and it's assumed +// that eviction is done by the caller. +func New(maxEntries int) *Cache { + return &Cache{ + MaxEntries: maxEntries, + ll: list.New(), + cache: make(map[interface{}]*list.Element), + } +} + +// Add adds a value to the cache. +func (c *Cache) Add(key Key, value interface{}) { + if c.cache == nil { + c.cache = make(map[interface{}]*list.Element) + c.ll = list.New() + } + if ee, ok := c.cache[key]; ok { + c.ll.MoveToFront(ee) + ee.Value.(*entry).value = value + return + } + ele := c.ll.PushFront(&entry{key, value}) + c.cache[key] = ele + if c.MaxEntries != 0 && c.ll.Len() > c.MaxEntries { + c.RemoveOldest() + } +} + +// Get looks up a key's value from the cache. +func (c *Cache) Get(key Key) (value interface{}, ok bool) { + if c.cache == nil { + return + } + if ele, hit := c.cache[key]; hit { + c.ll.MoveToFront(ele) + return ele.Value.(*entry).value, true + } + return +} + +// Remove removes the provided key from the cache. +func (c *Cache) Remove(key Key) { + if c.cache == nil { + return + } + if ele, hit := c.cache[key]; hit { + c.removeElement(ele) + } +} + +// RemoveOldest removes the oldest item from the cache. +func (c *Cache) RemoveOldest() { + if c.cache == nil { + return + } + ele := c.ll.Back() + if ele != nil { + c.removeElement(ele) + } +} + +func (c *Cache) removeElement(e *list.Element) { + c.ll.Remove(e) + kv := e.Value.(*entry) + delete(c.cache, kv.key) + if c.OnEvicted != nil { + c.OnEvicted(kv.key, kv.value) + } +} + +// Len returns the number of items in the cache. +func (c *Cache) Len() int { + if c.cache == nil { + return 0 + } + return c.ll.Len() +} diff --git a/Godeps/_workspace/src/github.com/golang/groupcache/lru/lru_test.go b/Godeps/_workspace/src/github.com/golang/groupcache/lru/lru_test.go new file mode 100644 index 0000000000000..98a2656e88340 --- /dev/null +++ b/Godeps/_workspace/src/github.com/golang/groupcache/lru/lru_test.go @@ -0,0 +1,73 @@ +/* +Copyright 2013 Google Inc. + +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 lru + +import ( + "testing" +) + +type simpleStruct struct { + int + string +} + +type complexStruct struct { + int + simpleStruct +} + +var getTests = []struct { + name string + keyToAdd interface{} + keyToGet interface{} + expectedOk bool +}{ + {"string_hit", "myKey", "myKey", true}, + {"string_miss", "myKey", "nonsense", false}, + {"simple_struct_hit", simpleStruct{1, "two"}, simpleStruct{1, "two"}, true}, + {"simeple_struct_miss", simpleStruct{1, "two"}, simpleStruct{0, "noway"}, false}, + {"complex_struct_hit", complexStruct{1, simpleStruct{2, "three"}}, + complexStruct{1, simpleStruct{2, "three"}}, true}, +} + +func TestGet(t *testing.T) { + for _, tt := range getTests { + lru := New(0) + lru.Add(tt.keyToAdd, 1234) + val, ok := lru.Get(tt.keyToGet) + if ok != tt.expectedOk { + t.Fatalf("%s: cache hit = %v; want %v", tt.name, ok, !ok) + } else if ok && val != 1234 { + t.Fatalf("%s expected get to return 1234 but got %v", tt.name, val) + } + } +} + +func TestRemove(t *testing.T) { + lru := New(0) + lru.Add("myKey", 1234) + if val, ok := lru.Get("myKey"); !ok { + t.Fatal("TestRemove returned no match") + } else if val != 1234 { + t.Fatalf("TestRemove failed. Expected %d, got %v", 1234, val) + } + + lru.Remove("myKey") + if _, ok := lru.Get("myKey"); ok { + t.Fatal("TestRemove returned a removed entry") + } +} diff --git a/pkg/client/record/event.go b/pkg/client/record/event.go index 2e86f07e421d8..cf6755caf790b 100644 --- a/pkg/client/record/event.go +++ b/pkg/client/record/event.go @@ -59,7 +59,7 @@ func StartRecording(recorder EventRecorder, source api.EventSource) watch.Interf event = &eventCopy event.Source = source - previousEvent := GetEvent(event) + previousEvent := getEvent(event) updateExistingEvent := previousEvent.Count > 0 if updateExistingEvent { event.Count = previousEvent.Count + 1 @@ -102,7 +102,7 @@ func recordEvent(recorder EventRecorder, event *api.Event, updateExistingEvent b newEvent, err = recorder.Create(event) } if err == nil { - AddOrUpdateEvent(newEvent) + addOrUpdateEvent(newEvent) return true } diff --git a/pkg/client/record/events_cache.go b/pkg/client/record/events_cache.go index be2a714325bd2..6243b5bcd8397 100644 --- a/pkg/client/record/events_cache.go +++ b/pkg/client/record/events_cache.go @@ -19,10 +19,11 @@ package record import ( "github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/util" + "github.com/golang/groupcache/lru" "sync" ) -type History struct { +type history struct { // The number of times the event has occured since first occurance. Count int @@ -36,36 +37,52 @@ type History struct { ResourceVersion string } -type historyMap struct { +const ( + maxLruCacheEntries = 4096 +) + +type historyCache struct { sync.RWMutex - table map[string]History + cache *lru.Cache } -var previousEvents = historyMap{table: make(map[string]History)} +var previousEvents = historyCache{cache: lru.New(maxLruCacheEntries)} -// AddOrUpdateEvent creates a new entry for the given event in the previous events hash table if the event +// addOrUpdateEvent creates a new entry for the given event in the previous events hash table if the event // doesn't already exist, otherwise it updates the existing entry. -func AddOrUpdateEvent(newEvent *api.Event) History { +func addOrUpdateEvent(newEvent *api.Event) history { key := getEventKey(newEvent) previousEvents.Lock() defer previousEvents.Unlock() - previousEvents.table[key] = - History{ + previousEvents.cache.Add( + key, + history{ Count: newEvent.Count, FirstTimestamp: newEvent.FirstTimestamp, Name: newEvent.Name, ResourceVersion: newEvent.ResourceVersion, - } - return previousEvents.table[key] + }) + return getEventFromCache(key) } -// GetEvent returns the entry corresponding to the given event, if one exists, otherwise a History object -// with a count of 1 is returned. -func GetEvent(event *api.Event) History { +// getEvent returns the entry corresponding to the given event, if one exists, otherwise a history object +// with a count of 0 is returned. +func getEvent(event *api.Event) history { key := getEventKey(event) previousEvents.RLock() defer previousEvents.RUnlock() - return previousEvents.table[key] + return getEventFromCache(key) +} + +func getEventFromCache(key string) history { + value, ok := previousEvents.cache.Get(key) + if ok { + historyValue, ok := value.(history) + if ok { + return historyValue + } + } + return history{} } func getEventKey(event *api.Event) string { diff --git a/pkg/client/record/events_cache_test.go b/pkg/client/record/events_cache_test.go index 00bc4e07bc4a7..9fe5ccea7b4b2 100644 --- a/pkg/client/record/events_cache_test.go +++ b/pkg/client/record/events_cache_test.go @@ -45,7 +45,7 @@ func TestAddOrUpdateEventNoExisting(t *testing.T) { } // Act - result := AddOrUpdateEvent(&event) + result := addOrUpdateEvent(&event) // Assert compareEventWithHistoryEntry(&event, &result, t) @@ -99,9 +99,9 @@ func TestAddOrUpdateEventExisting(t *testing.T) { } // Act - AddOrUpdateEvent(&event1) - result1 := AddOrUpdateEvent(&event2) - result2 := GetEvent(&event1) + addOrUpdateEvent(&event1) + result1 := addOrUpdateEvent(&event2) + result2 := getEvent(&event1) // Assert compareEventWithHistoryEntry(&event2, &result1, t) @@ -128,7 +128,7 @@ func TestGetEventNoExisting(t *testing.T) { } // Act - existingEvent := GetEvent(&event) + existingEvent := getEvent(&event) // Assert if existingEvent.Count != 0 { @@ -157,16 +157,16 @@ func TestGetEventExisting(t *testing.T) { FirstTimestamp: eventTime, LastTimestamp: eventTime, } - AddOrUpdateEvent(&event) + addOrUpdateEvent(&event) // Act - existingEvent := GetEvent(&event) + existingEvent := getEvent(&event) // Assert compareEventWithHistoryEntry(&event, &existingEvent, t) } -func compareEventWithHistoryEntry(expected *api.Event, actual *History, t *testing.T) { +func compareEventWithHistoryEntry(expected *api.Event, actual *history, t *testing.T) { if actual.Count != expected.Count { t.Fatalf("There should be one existing instance of this event in the hash table.")