File: System\Diagnostics\Reader\ProviderMetadataCachedInformation.cs
Web Access
Project: src\src\runtime\src\libraries\System.Diagnostics.EventLog\src\System.Diagnostics.EventLog.csproj (System.Diagnostics.EventLog)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using Microsoft.Win32;

namespace System.Diagnostics.Eventing.Reader
{
    /// <summary>
    /// This class does not expose underlying Provider metadata objects. Instead it
    /// exposes a limited set of Provider metadata information from the cache. The reason
    /// for this is so the cache can easily Dispose the metadata object without worrying
    /// about who is using it.
    /// </summary>
    internal sealed class ProviderMetadataCachedInformation
    {
        private readonly Dictionary<ProviderMetadataId, CacheItem> _cache;
        private readonly int _maximumCacheSize;
        private readonly EventLogSession _session;
        private readonly string? _logfile;

        private sealed class ProviderMetadataId
        {
            public ProviderMetadataId(string providerName, CultureInfo cultureInfo)
            {
                ProviderName = providerName;
                TheCultureInfo = cultureInfo;
            }

            public override bool Equals([NotNullWhen(true)] object? obj)
            {
                ProviderMetadataId? rhs = obj as ProviderMetadataId;
                if (rhs == null)
                    return false;
                if (ProviderName.Equals(rhs.ProviderName) && (TheCultureInfo == rhs.TheCultureInfo))
                    return true;
                return false;
            }

            public override int GetHashCode()
            {
                return ProviderName.GetHashCode() ^ TheCultureInfo.GetHashCode();
            }

            public string ProviderName { get; }
            public CultureInfo TheCultureInfo { get; }
        }

        private sealed class CacheItem
        {
            public CacheItem(ProviderMetadata pm)
            {
                ProviderMetadata = pm;
                TheTime = DateTime.Now;
            }

            public DateTime TheTime { get; set; }

            public ProviderMetadata ProviderMetadata { get; }
        }

        public ProviderMetadataCachedInformation(EventLogSession session, string? logfile, int maximumCacheSize)
        {
            Debug.Assert(session != null);
            _session = session;
            _logfile = logfile;
            _cache = new Dictionary<ProviderMetadataId, CacheItem>();
            _maximumCacheSize = maximumCacheSize;
        }

        private bool IsCacheFull()
        {
            return _cache.Count == _maximumCacheSize;
        }

        private bool IsProviderinCache(ProviderMetadataId key)
        {
            return _cache.ContainsKey(key);
        }

        private void DeleteCacheEntry(ProviderMetadataId key)
        {
            if (!IsProviderinCache(key))
                return;

            CacheItem value = _cache[key];
            _cache.Remove(key);

            value.ProviderMetadata.Dispose();
        }

        private void AddCacheEntry(ProviderMetadataId key, ProviderMetadata pm)
        {
            if (IsCacheFull())
                FlushOldestEntry();

            CacheItem value = new CacheItem(pm);
            _cache.Add(key, value);
            return;
        }

        private void FlushOldestEntry()
        {
            double maxPassedTime = -10;
            DateTime timeNow = DateTime.Now;
            ProviderMetadataId? keyToDelete = null;

            // Get the entry in the cache which was not accessed for the longest time.
            foreach (KeyValuePair<ProviderMetadataId, CacheItem> kvp in _cache)
            {
                // The time difference (in ms) between the timeNow and the last used time of each entry
                TimeSpan timeDifference = timeNow.Subtract(kvp.Value.TheTime);

                // For the "unused" items (with ReferenceCount == 0)   -> can possible be deleted.
                if (timeDifference.TotalMilliseconds >= maxPassedTime)
                {
                    maxPassedTime = timeDifference.TotalMilliseconds;
                    keyToDelete = kvp.Key;
                }
            }

            if (keyToDelete != null)
                DeleteCacheEntry(keyToDelete);
        }

        private static void UpdateCacheValueInfoForHit(CacheItem cacheItem)
        {
            cacheItem.TheTime = DateTime.Now;
        }

        private ProviderMetadata GetProviderMetadata(ProviderMetadataId key)
        {
            if (!IsProviderinCache(key))
            {
                ProviderMetadata pm;
                try
                {
                    pm = new ProviderMetadata(key.ProviderName, _session, key.TheCultureInfo, _logfile);
                }
                catch (EventLogNotFoundException)
                {
                    pm = new ProviderMetadata(key.ProviderName, _session, key.TheCultureInfo);
                }
                AddCacheEntry(key, pm);
                return pm;
            }
            else
            {
                CacheItem cacheItem = _cache[key];
                ProviderMetadata pm = cacheItem.ProviderMetadata;

                // check Provider metadata to be sure it's hasn't been
                // uninstalled since last time it was used.

                try
                {
                    UpdateCacheValueInfoForHit(cacheItem);
                }
                catch (EventLogException)
                {
                    DeleteCacheEntry(key);
                    try
                    {
                        pm = new ProviderMetadata(key.ProviderName, _session, key.TheCultureInfo, _logfile);
                    }
                    catch (EventLogNotFoundException)
                    {
                        pm = new ProviderMetadata(key.ProviderName, _session, key.TheCultureInfo);
                    }
                    AddCacheEntry(key, pm);
                }

                return pm;
            }
        }

        public string? GetFormatDescription(string ProviderName, EventLogHandle eventHandle)
        {
            lock (this)
            {
                ProviderMetadataId key = new ProviderMetadataId(ProviderName, CultureInfo.CurrentCulture);

                try
                {
                    ProviderMetadata pm = GetProviderMetadata(key);
                    return NativeWrapper.EvtFormatMessageRenderName(pm.Handle, eventHandle, UnsafeNativeMethods.EvtFormatMessageFlags.EvtFormatMessageEvent);
                }
                catch (EventLogNotFoundException)
                {
                    return null;
                }
            }
        }

        public string? GetFormatDescription(string ProviderName, EventLogHandle eventHandle, string?[] values)
        {
            lock (this)
            {
                ProviderMetadataId key = new ProviderMetadataId(ProviderName, CultureInfo.CurrentCulture);
                ProviderMetadata pm = GetProviderMetadata(key);
                try
                {
                    return NativeWrapper.EvtFormatMessageFormatDescription(pm.Handle, eventHandle, values);
                }
                catch (EventLogNotFoundException)
                {
                    return null;
                }
            }
        }

        public string? GetLevelDisplayName(string ProviderName, EventLogHandle eventHandle)
        {
            lock (this)
            {
                ProviderMetadataId key = new ProviderMetadataId(ProviderName, CultureInfo.CurrentCulture);
                ProviderMetadata pm = GetProviderMetadata(key);
                return NativeWrapper.EvtFormatMessageRenderName(pm.Handle, eventHandle, UnsafeNativeMethods.EvtFormatMessageFlags.EvtFormatMessageLevel);
            }
        }

        public string? GetOpcodeDisplayName(string ProviderName, EventLogHandle eventHandle)
        {
            lock (this)
            {
                ProviderMetadataId key = new ProviderMetadataId(ProviderName, CultureInfo.CurrentCulture);
                ProviderMetadata pm = GetProviderMetadata(key);
                return NativeWrapper.EvtFormatMessageRenderName(pm.Handle, eventHandle, UnsafeNativeMethods.EvtFormatMessageFlags.EvtFormatMessageOpcode);
            }
        }

        public string? GetTaskDisplayName(string ProviderName, EventLogHandle eventHandle)
        {
            lock (this)
            {
                ProviderMetadataId key = new ProviderMetadataId(ProviderName, CultureInfo.CurrentCulture);
                ProviderMetadata pm = GetProviderMetadata(key);
                return NativeWrapper.EvtFormatMessageRenderName(pm.Handle, eventHandle, UnsafeNativeMethods.EvtFormatMessageFlags.EvtFormatMessageTask);
            }
        }

        public IEnumerable<string> GetKeywordDisplayNames(string ProviderName, EventLogHandle eventHandle)
        {
            lock (this)
            {
                ProviderMetadataId key = new ProviderMetadataId(ProviderName, CultureInfo.CurrentCulture);
                ProviderMetadata pm = GetProviderMetadata(key);
                return NativeWrapper.EvtFormatMessageRenderKeywords(pm.Handle, eventHandle, UnsafeNativeMethods.EvtFormatMessageFlags.EvtFormatMessageKeyword);
            }
        }
    }
}