youtubebeat/vendor/github.com/elastic/beats/winlogbeat/eventlog/cache.go

151 lines
4.8 KiB
Go

// 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)
}
}
}