// Licensed to Elasticsearch B.V. under one or more contributor // license agreements. See the NOTICE file distributed with // this work for additional information regarding copyright // ownership. Elasticsearch B.V. licenses this file to you 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 eventlog // This component of the eventlog package provides a cache for storing Handles // to event message files. import ( "expvar" "time" "github.com/elastic/beats/libbeat/common" "github.com/elastic/beats/libbeat/logp" "github.com/elastic/beats/winlogbeat/sys" ) // Stats for the message file caches. var ( cacheStats = expvar.NewMap("msg_file_cache") ) // Constants that control the cache behavior. const ( expirationTimeout time.Duration = 2 * time.Minute janitorInterval time.Duration = 30 * time.Second initialSize int = 10 ) // Function type for loading event message files associated with the given // event log and source name. type messageFileLoaderFunc func(eventLogName, sourceName string) sys.MessageFiles // Function type for freeing Handles. type freeHandleFunc func(handle uintptr) error // handleCache provides a synchronized cache that holds MessageFiles. type messageFilesCache struct { cache *common.Cache loader messageFileLoaderFunc freer freeHandleFunc eventLogName string // Cache metrics. hit func() // Increments number of cache hits. miss func() // Increments number of cache misses. size func() // Sets the current cache size. } // newHandleCache creates and returns a new handleCache that has been // initialized (including starting a periodic janitor goroutine to purge // expired Handles). func newMessageFilesCache(eventLogName string, loader messageFileLoaderFunc, freer freeHandleFunc) *messageFilesCache { size := &expvar.Int{} cacheStats.Set(eventLogName+"Size", size) hc := &messageFilesCache{ loader: loader, freer: freer, eventLogName: eventLogName, hit: func() { cacheStats.Add(eventLogName+"Hits", 1) }, miss: func() { cacheStats.Add(eventLogName+"Misses", 1) }, } hc.cache = common.NewCacheWithRemovalListener(expirationTimeout, initialSize, hc.evictionHandler) hc.cache.StartJanitor(janitorInterval) hc.size = func() { s := hc.cache.Size() size.Set(int64(s)) debugf("messageFilesCache[%s] size=%d", hc.eventLogName, s) } return hc } // get returns a cached MessageFiles for the given sourceName. // If no item is cached, then one is loaded, stored, and returned. // Callers should check the MessageFiles.Err value to see if an error occurred // while loading the message files. func (hc *messageFilesCache) get(sourceName string) sys.MessageFiles { v := hc.cache.Get(sourceName) if v == nil { hc.miss() // Handle to event message file for sourceName is not cached. Attempt // to load the Handles into the cache. v = hc.loader(hc.eventLogName, sourceName) // Store the newly loaded value. Since this code does not lock we must // check if a value was already loaded. existing := hc.cache.PutIfAbsent(sourceName, v) if existing != nil { // A value was already loaded, so free the handles we just created. messageFiles, _ := v.(sys.MessageFiles) hc.freeHandles(messageFiles) // Return the existing cached value. messageFiles, _ = existing.(sys.MessageFiles) return messageFiles } hc.size() } else { hc.hit() } messageFiles, _ := v.(sys.MessageFiles) return messageFiles } // evictionHandler is the callback handler that receives notifications when // a key-value pair is evicted from the messageFilesCache. func (hc *messageFilesCache) evictionHandler(k common.Key, v common.Value) { // Update the size on a different goroutine after the callback completes. defer func() { go hc.size() }() messageFiles, ok := v.(sys.MessageFiles) if !ok { return } debugf("messageFilesCache[%s] Evicting messageFiles %+v for sourceName %v.", hc.eventLogName, messageFiles, k) hc.freeHandles(messageFiles) } // freeHandles free the event message file Handles so that the modules can // be unloaded. The Handles are no longer valid after being freed. func (hc *messageFilesCache) freeHandles(mf sys.MessageFiles) { for _, fh := range mf.Handles { err := hc.freer(fh.Handle) if err != nil { logp.Warn("messageFilesCache[%s] FreeLibrary error for handle %v", hc.eventLogName, fh.Handle) } } }