From 5dd8f29e6f528b0220411724cfa851c459e5df9a Mon Sep 17 00:00:00 2001 From: Nathan Comben Date: Mon, 22 Nov 2021 17:08:01 +0000 Subject: [PATCH 1/3] Add Application Inisghts telemetry wrapper to Redis distributed caching --- CacheMeIfYouCan.sln | 42 +++++---- .../DistributedCachePollyWrapper.cs | 10 +++ .../CacheMeIfYouCan.Redis.csproj | 4 + ...hedObjectConfigurationManagerExtensions.cs | 23 +++++ .../IRedisTelemetryConfig.cs | 7 ++ src/CacheMeIfYouCan.Redis/RedisCache.cs | 85 +++++++++++-------- src/CacheMeIfYouCan.Redis/RedisTelemetry.cs | 53 ++++++++++++ .../RedisTelemetryProcessor.cs | 28 ++++++ src/CacheMeIfYouCan/CacheMeIfYouCan.csproj | 7 +- .../DistributedCacheEventsWrapperBase.cs | 10 +++ src/CacheMeIfYouCan/IDistributedCache.cs | 5 ++ src/CacheMeIfYouCan/IRedisTelemetry.cs | 10 +++ test.runsettings | 10 +++ .../CacheMeIfYouCan.Redis.Tests.csproj | 1 + .../MockTelemetry.cs | 13 +++ .../MockTelemetryProcessor.cs | 42 +++++++++ .../RedisCacheTests1.cs | 43 +++++++++- .../RedisCacheTests2.cs | 40 ++++++++- .../MockDistributedCache.cs | 8 ++ 19 files changed, 385 insertions(+), 56 deletions(-) create mode 100644 src/CacheMeIfYouCan.Redis/CachedObjectConfigurationManagerExtensions.cs create mode 100644 src/CacheMeIfYouCan.Redis/IRedisTelemetryConfig.cs create mode 100644 src/CacheMeIfYouCan.Redis/RedisTelemetry.cs create mode 100644 src/CacheMeIfYouCan.Redis/RedisTelemetryProcessor.cs create mode 100644 src/CacheMeIfYouCan/IRedisTelemetry.cs create mode 100644 test.runsettings create mode 100644 tests/CacheMeIfYouCan.Redis.Tests/MockTelemetry.cs create mode 100644 tests/CacheMeIfYouCan.Redis.Tests/MockTelemetryProcessor.cs diff --git a/CacheMeIfYouCan.sln b/CacheMeIfYouCan.sln index e274bc7c..8bb01f9a 100644 --- a/CacheMeIfYouCan.sln +++ b/CacheMeIfYouCan.sln @@ -1,32 +1,40 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CacheMeIfYouCan", "src\CacheMeIfYouCan\CacheMeIfYouCan.csproj", "{0E5C541F-0B77-4073-AEFE-0BA03EC3CA0A}" +# Visual Studio Version 16 +VisualStudioVersion = 16.0.31829.152 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CacheMeIfYouCan", "src\CacheMeIfYouCan\CacheMeIfYouCan.csproj", "{0E5C541F-0B77-4073-AEFE-0BA03EC3CA0A}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CacheMeIfYouCan.Tests", "tests\CacheMeIfYouCan.Tests\CacheMeIfYouCan.Tests.csproj", "{B4C0DCD9-FFE4-44B9-8457-37262FC5318E}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CacheMeIfYouCan.Tests", "tests\CacheMeIfYouCan.Tests\CacheMeIfYouCan.Tests.csproj", "{B4C0DCD9-FFE4-44B9-8457-37262FC5318E}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CacheMeIfYouCan.ILTemplates", "sandbox\CacheMeIfYouCan.ILTemplates\CacheMeIfYouCan.ILTemplates.csproj", "{42055B9A-138C-4114-AD06-6BAA3ABBC659}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CacheMeIfYouCan.ILTemplates", "sandbox\CacheMeIfYouCan.ILTemplates\CacheMeIfYouCan.ILTemplates.csproj", "{42055B9A-138C-4114-AD06-6BAA3ABBC659}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CacheMeIfYouCan.Redis", "src\CacheMeIfYouCan.Redis\CacheMeIfYouCan.Redis.csproj", "{EB0E7F39-18FD-46F3-800B-EC98C38055A7}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CacheMeIfYouCan.Redis", "src\CacheMeIfYouCan.Redis\CacheMeIfYouCan.Redis.csproj", "{EB0E7F39-18FD-46F3-800B-EC98C38055A7}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CacheMeIfYouCan.Redis.Tests", "tests\CacheMeIfYouCan.Redis.Tests\CacheMeIfYouCan.Redis.Tests.csproj", "{09EB8E7F-EE8E-432F-8108-10255FE234EB}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CacheMeIfYouCan.Redis.Tests", "tests\CacheMeIfYouCan.Redis.Tests\CacheMeIfYouCan.Redis.Tests.csproj", "{09EB8E7F-EE8E-432F-8108-10255FE234EB}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CacheMeIfYouCan.Polly", "src\CacheMeIfYouCan.Polly\CacheMeIfYouCan.Polly.csproj", "{7A3AFBE2-4D42-4C20-BB75-C2F01D0C36D1}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CacheMeIfYouCan.Polly", "src\CacheMeIfYouCan.Polly\CacheMeIfYouCan.Polly.csproj", "{7A3AFBE2-4D42-4C20-BB75-C2F01D0C36D1}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CacheMeIfYouCan.Polly.Tests", "tests\CacheMeIfYouCan.Polly.Tests\CacheMeIfYouCan.Polly.Tests.csproj", "{AF8803FC-0559-40E6-A902-72C0A77DF380}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CacheMeIfYouCan.Polly.Tests", "tests\CacheMeIfYouCan.Polly.Tests\CacheMeIfYouCan.Polly.Tests.csproj", "{AF8803FC-0559-40E6-A902-72C0A77DF380}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CacheMeIfYouCan.Serializers.ProtoBuf", "src\CacheMeIfYouCan.Serializers.ProtoBuf\CacheMeIfYouCan.Serializers.ProtoBuf.csproj", "{4B7C629B-F976-47F9-8721-5DC8DD60B8B4}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CacheMeIfYouCan.Serializers.ProtoBuf", "src\CacheMeIfYouCan.Serializers.ProtoBuf\CacheMeIfYouCan.Serializers.ProtoBuf.csproj", "{4B7C629B-F976-47F9-8721-5DC8DD60B8B4}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CacheMeIfYouCan.Benchmarks", "benchmarks\CacheMeIfYouCan.Benchmarks\CacheMeIfYouCan.Benchmarks.csproj", "{C133ACD8-ABF2-4DBA-9907-49726AF4AF43}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CacheMeIfYouCan.Benchmarks", "benchmarks\CacheMeIfYouCan.Benchmarks\CacheMeIfYouCan.Benchmarks.csproj", "{C133ACD8-ABF2-4DBA-9907-49726AF4AF43}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CacheMeIfYouCan.Cron", "src\CacheMeIfYouCan.Cron\CacheMeIfYouCan.Cron.csproj", "{3C906ADB-686F-46B4-859C-E425040F1CFE}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CacheMeIfYouCan.Cron", "src\CacheMeIfYouCan.Cron\CacheMeIfYouCan.Cron.csproj", "{3C906ADB-686F-46B4-859C-E425040F1CFE}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CacheMeIfYouCan.Cron.Tests", "tests\CacheMeIfYouCan.Cron.Tests\CacheMeIfYouCan.Cron.Tests.csproj", "{A0F382D8-3C5A-4955-A9CA-B558EEDBAD72}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CacheMeIfYouCan.Cron.Tests", "tests\CacheMeIfYouCan.Cron.Tests\CacheMeIfYouCan.Cron.Tests.csproj", "{A0F382D8-3C5A-4955-A9CA-B558EEDBAD72}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CacheMeIfYouCan.Serializers.Json", "src\CacheMeIfYouCan.Serializers.Json\CacheMeIfYouCan.Serializers.Json.csproj", "{FC6B01A7-3049-44A4-833B-31B0961D50C1}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CacheMeIfYouCan.Serializers.Json", "src\CacheMeIfYouCan.Serializers.Json\CacheMeIfYouCan.Serializers.Json.csproj", "{FC6B01A7-3049-44A4-833B-31B0961D50C1}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CacheMeIfYouCan.Serializers.Tests", "tests\CacheMeIfYouCan.Serializers.Tests\CacheMeIfYouCan.Serializers.Tests.csproj", "{8A13A189-17C8-42A3-86DD-E1DC6BF23E56}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CacheMeIfYouCan.Serializers.Tests", "tests\CacheMeIfYouCan.Serializers.Tests\CacheMeIfYouCan.Serializers.Tests.csproj", "{8A13A189-17C8-42A3-86DD-E1DC6BF23E56}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CacheMeIfYouCan.Serializers.MessagePack", "src\CacheMeIfYouCan.Serializers.MessagePack\CacheMeIfYouCan.Serializers.MessagePack.csproj", "{CDA877B2-5E3A-4D48-8650-D1B20AF773D8}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CacheMeIfYouCan.Serializers.MessagePack", "src\CacheMeIfYouCan.Serializers.MessagePack\CacheMeIfYouCan.Serializers.MessagePack.csproj", "{CDA877B2-5E3A-4D48-8650-D1B20AF773D8}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{200A2BA5-847A-46CD-80AF-5E09BCF5D57E}" + ProjectSection(SolutionItems) = preProject + test.runsettings = test.runsettings + EndProjectSection EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -91,6 +99,10 @@ Global {CDA877B2-5E3A-4D48-8650-D1B20AF773D8}.Release|Any CPU.ActiveCfg = Release|Any CPU {CDA877B2-5E3A-4D48-8650-D1B20AF773D8}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection - GlobalSection(NestedProjects) = preSolution + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {930E63DC-AC1E-4694-BE8E-3F268FBE9630} EndGlobalSection EndGlobal diff --git a/src/CacheMeIfYouCan.Polly/DistributedCachePollyWrapper.cs b/src/CacheMeIfYouCan.Polly/DistributedCachePollyWrapper.cs index 8fd54bc4..7d1b2c5a 100644 --- a/src/CacheMeIfYouCan.Polly/DistributedCachePollyWrapper.cs +++ b/src/CacheMeIfYouCan.Polly/DistributedCachePollyWrapper.cs @@ -68,6 +68,11 @@ public Task TryRemove(TKey key) ? _innerCache.TryRemove(key) : _tryRemovePolicy.ExecuteAsync(() => _innerCache.TryRemove(key)); } + + public void SetTelemetry(IRedisTelemetry redisTelemetry) + { + _innerCache.SetTelemetry(redisTelemetry); + } } public sealed class DistributedCachePollyWrapper : @@ -122,5 +127,10 @@ public Task TryRemove(TOuterKey outerKey, TInnerKey innerKey) ? _innerCache.TryRemove(outerKey, innerKey) : _tryRemovePolicy.ExecuteAsync(() => _innerCache.TryRemove(outerKey, innerKey)); } + + public void SetTelemetry(IRedisTelemetry redisTelemetry) + { + _innerCache.SetTelemetry(redisTelemetry); + } } } \ No newline at end of file diff --git a/src/CacheMeIfYouCan.Redis/CacheMeIfYouCan.Redis.csproj b/src/CacheMeIfYouCan.Redis/CacheMeIfYouCan.Redis.csproj index 07df0ec0..53ca7eeb 100644 --- a/src/CacheMeIfYouCan.Redis/CacheMeIfYouCan.Redis.csproj +++ b/src/CacheMeIfYouCan.Redis/CacheMeIfYouCan.Redis.csproj @@ -6,6 +6,7 @@ Extends CacheMeIfYouCan by providing Redis cache integration + @@ -13,4 +14,7 @@ + + + diff --git a/src/CacheMeIfYouCan.Redis/CachedObjectConfigurationManagerExtensions.cs b/src/CacheMeIfYouCan.Redis/CachedObjectConfigurationManagerExtensions.cs new file mode 100644 index 00000000..e97711ef --- /dev/null +++ b/src/CacheMeIfYouCan.Redis/CachedObjectConfigurationManagerExtensions.cs @@ -0,0 +1,23 @@ +using Microsoft.ApplicationInsights.Extensibility; + +namespace CacheMeIfYouCan.Redis +{ + public static class CachedObjectConfigurationManagerExtensions + { + public static IDistributedCache WithAppInsightsTelemetry(this IDistributedCache redisCache, + ITelemetryProcessor telemetryProcessor, IRedisTelemetryConfig redisTelemetryConfig, string hostName, string cacheName) + { + var redisTelemetry = new RedisTelemetry(telemetryProcessor, redisTelemetryConfig, hostName, cacheName); + redisCache.SetTelemetry(redisTelemetry); + return redisCache; + } + + public static IDistributedCache WithAppInsightsTelemetry(this IDistributedCache redisCache, + ITelemetryProcessor telemetryProcessor, IRedisTelemetryConfig redisTelemetryConfig, string hostName, string cacheName) + { + var redisTelemetry = new RedisTelemetry(telemetryProcessor, redisTelemetryConfig, hostName, cacheName); + redisCache.SetTelemetry(redisTelemetry); + return redisCache; + } + } +} diff --git a/src/CacheMeIfYouCan.Redis/IRedisTelemetryConfig.cs b/src/CacheMeIfYouCan.Redis/IRedisTelemetryConfig.cs new file mode 100644 index 00000000..b47f990e --- /dev/null +++ b/src/CacheMeIfYouCan.Redis/IRedisTelemetryConfig.cs @@ -0,0 +1,7 @@ +namespace CacheMeIfYouCan.Redis +{ + public interface IRedisTelemetryConfig + { + public int MillisecondThreshold { get; } + } +} \ No newline at end of file diff --git a/src/CacheMeIfYouCan.Redis/RedisCache.cs b/src/CacheMeIfYouCan.Redis/RedisCache.cs index ac052116..8ef17aba 100644 --- a/src/CacheMeIfYouCan.Redis/RedisCache.cs +++ b/src/CacheMeIfYouCan.Redis/RedisCache.cs @@ -8,10 +8,10 @@ using System.Threading; using System.Threading.Tasks; using CacheMeIfYouCan.Redis.Internal; -using Microsoft.IO; + using Microsoft.IO; using StackExchange.Redis; -namespace CacheMeIfYouCan.Redis + namespace CacheMeIfYouCan.Redis { public sealed class RedisCache : IDistributedCache, IDisposable { @@ -26,6 +26,12 @@ public sealed class RedisCache : IDistributedCache, private readonly RecentlySetOrRemovedKeysManager _recentlyRemovedKeysManager; private readonly int _keyPrefixLength; private bool _disposed; + private IRedisTelemetry _redisTelemetry = new NoTelemetry(); + + public void SetTelemetry(IRedisTelemetry redisTelemetry) + { + _redisTelemetry = redisTelemetry; + } public RedisCache( IRedisConnection connection, @@ -121,13 +127,12 @@ private RedisCache( public async Task<(bool Success, ValueAndTimeToLive Value)> TryGet(TKey key) { CheckDisposed(); - - var redisDb = GetDatabase(); var redisKey = _keySerializer(key); - - var fromRedis = await redisDb - .StringGetWithExpiryAsync(redisKey) + + var fromRedis = await _redisTelemetry.CallRedisAsync( + () => GetDatabase().StringGetWithExpiryAsync(redisKey), + "StringGetWithExpiryAsync", redisKey) .ConfigureAwait(false); if (fromRedis.Value.IsNull) @@ -141,8 +146,6 @@ private RedisCache( public async Task Set(TKey key, TValue value, TimeSpan timeToLive) { CheckDisposed(); - - var redisDb = GetDatabase(); var redisKey = _keySerializer(key); @@ -153,7 +156,10 @@ public async Task Set(TKey key, TValue value, TimeSpan timeToLive) _recentlySetKeysManager?.Mark(((string)redisKey).Substring(_keyPrefixLength)); - var task = redisDb.StringSetAsync(redisKey, redisValue, timeToLive, flags: _setValueFlags); + var task = _redisTelemetry.CallRedisAsync( + () => GetDatabase().StringSetAsync(redisKey, redisValue, timeToLive, flags: _setValueFlags), + "StringSetAsync", + redisKey); if (!task.IsCompleted) await task.ConfigureAwait(false); @@ -169,8 +175,6 @@ public async Task GetMany( Memory>> destination) { CheckDisposed(); - - var redisDb = GetDatabase(); var valuesFoundCount = 0; @@ -204,8 +208,11 @@ await Task async Task<(TKey, RedisValueWithExpiry)> GetSingle(TKey key) { - var fromRedis = await redisDb - .StringGetWithExpiryAsync(_keySerializer(key)) + var redisKey = _keySerializer(key); + var fromRedis = await _redisTelemetry.CallRedisAsync( + async () => await GetDatabase() + .StringGetWithExpiryAsync(redisKey) + .ConfigureAwait(false), "StringGetWithExpiryAsync", redisKey) .ConfigureAwait(false); if (!fromRedis.Value.IsNull) @@ -242,8 +249,6 @@ int CopyResultsToDestinationArray() public async Task SetMany(ReadOnlyMemory> values, TimeSpan timeToLive) { CheckDisposed(); - - var redisDb = GetDatabase(); var pooledTasksArray = ArrayPool.Shared.Rent(values.Length); var toDispose = _serializeValuesToStreams @@ -286,7 +291,9 @@ IReadOnlyCollection CreateTasks() _recentlySetKeysManager?.Mark(((string)redisKey).Substring(_keyPrefixLength)); - pooledTasksArray[index++] = redisDb.StringSetAsync(redisKey, redisValue, timeToLive, flags: _setValueFlags); + pooledTasksArray[index++] = _redisTelemetry.CallRedisAsync( + () => GetDatabase().StringSetAsync(redisKey, redisValue, timeToLive, flags: _setValueFlags), + "StringSetAsync", redisKey); } return new ArraySegment(pooledTasksArray, 0, index); @@ -296,15 +303,14 @@ IReadOnlyCollection CreateTasks() public async Task TryRemove(TKey key) { CheckDisposed(); - - var redisDb = GetDatabase(); - + var redisKey = _keySerializer(key); _recentlyRemovedKeysManager?.Mark(((string)redisKey).Substring(_keyPrefixLength)); - - return await redisDb - .KeyDeleteAsync(redisKey) + + return await _redisTelemetry.CallRedisAsync( + () => GetDatabase().KeyDeleteAsync(redisKey), + "KeyDeleteAsync", redisKey) .ConfigureAwait(false); } @@ -364,6 +370,12 @@ public sealed class RedisCache : IDistributedCache private readonly RecentlySetOrRemovedKeysManager _recentlyRemovedKeysManager; private readonly int _keyPrefixLength; private bool _disposed; + private IRedisTelemetry _redisTelemetry = new NoTelemetry(); + + public void SetTelemetry(IRedisTelemetry redisTelemetry) + { + _redisTelemetry = redisTelemetry; + } public RedisCache( IRedisConnection connection, @@ -473,8 +485,6 @@ public async Task GetMany( { CheckDisposed(); - var redisDb = GetDatabase(); - var redisKeyPrefix = _outerKeySerializer(outerKey).Append(_keySeparator); var valuesFoundCount = 0; @@ -510,9 +520,11 @@ await Task async Task<(TInnerKey, RedisValueWithExpiry)> GetSingle(TInnerKey innerKey) { var redisKey = redisKeyPrefix.Append(_innerKeySerializer(innerKey)); - - var fromRedis = await redisDb - .StringGetWithExpiryAsync(redisKey) + + var fromRedis = await _redisTelemetry.CallRedisAsync( + async () => await GetDatabase() + .StringGetWithExpiryAsync(redisKey) + .ConfigureAwait(false), "StringGetWithExpiryAsync", redisKey) .ConfigureAwait(false); if (!fromRedis.Value.IsNull) @@ -553,8 +565,6 @@ public async Task SetMany( { CheckDisposed(); - var redisDb = GetDatabase(); - var redisKeyPrefix = _outerKeySerializer(outerKey).Append(_keySeparator); var pooledTasksArray = ArrayPool.Shared.Rent(values.Length); @@ -601,7 +611,9 @@ IReadOnlyCollection CreateTasks() _recentlySetKeysManager?.Mark(((string)redisKey).Substring(_keyPrefixLength)); - pooledTasksArray[index++] = redisDb.StringSetAsync(redisKey, redisValue, timeToLive, flags: _setValueFlags); + pooledTasksArray[index++] = _redisTelemetry.CallRedisAsync( + () => GetDatabase().StringSetAsync(redisKey, redisValue, timeToLive, flags: _setValueFlags), + "StringSetAsync", redisKey); } return new ArraySegment(pooledTasksArray, 0, index); @@ -612,16 +624,17 @@ public async Task TryRemove(TOuterKey outerKey, TInnerKey innerKey) { CheckDisposed(); - var redisDb = GetDatabase(); - var redisKey = _outerKeySerializer(outerKey) .Append(_keySeparator) .Append(_innerKeySerializer(innerKey)); _recentlyRemovedKeysManager?.Mark(((string)redisKey).Substring(_keyPrefixLength)); - - return await redisDb - .KeyDeleteAsync(redisKey) + + return await _redisTelemetry.CallRedisAsync( + async () => await GetDatabase() + .KeyDeleteAsync(redisKey) + .ConfigureAwait(false), + "KeyDeleteAsync", redisKey) .ConfigureAwait(false); } diff --git a/src/CacheMeIfYouCan.Redis/RedisTelemetry.cs b/src/CacheMeIfYouCan.Redis/RedisTelemetry.cs new file mode 100644 index 00000000..fdd58819 --- /dev/null +++ b/src/CacheMeIfYouCan.Redis/RedisTelemetry.cs @@ -0,0 +1,53 @@ +using System; +using System.Threading.Tasks; +using Microsoft.ApplicationInsights.DataContracts; +using Microsoft.ApplicationInsights.Extensibility; + +namespace CacheMeIfYouCan.Redis +{ + internal class RedisTelemetry : IRedisTelemetry + { + private readonly ITelemetryProcessor _telemetryProcessor; + private readonly string _host; + private readonly string _cacheName; + + public RedisTelemetry(ITelemetryProcessor telemetryProcessor, IRedisTelemetryConfig redisTelemetryConfig, string host, string cacheName) + { + _telemetryProcessor = new RedisTelemetryProcessor(telemetryProcessor, redisTelemetryConfig); + _host = host; + _cacheName = cacheName; + } + + public async Task CallRedisAsync(Func> func, string command, string key) + { + var success = false; + var commandInfoText = $"Command {command}{Environment.NewLine}Key '{key}'"; + + T result; + var startTime = DateTime.UtcNow; + var timer = System.Diagnostics.Stopwatch.StartNew(); + try + { + // making dependency call + result = await func().ConfigureAwait(false); + success = true; + } + finally + { + timer.Stop(); + _telemetryProcessor.Process( + new DependencyTelemetry("Redis", _host, _cacheName, commandInfoText, startTime, timer.Elapsed, "", + success)); + } + return result; + } + } + + internal class NoTelemetry : IRedisTelemetry + { + public async Task CallRedisAsync(Func> func, string command, string key) + { + return await func().ConfigureAwait(false); + } + } +} \ No newline at end of file diff --git a/src/CacheMeIfYouCan.Redis/RedisTelemetryProcessor.cs b/src/CacheMeIfYouCan.Redis/RedisTelemetryProcessor.cs new file mode 100644 index 00000000..2e4a4626 --- /dev/null +++ b/src/CacheMeIfYouCan.Redis/RedisTelemetryProcessor.cs @@ -0,0 +1,28 @@ +using Microsoft.ApplicationInsights.Channel; +using Microsoft.ApplicationInsights.DataContracts; +using Microsoft.ApplicationInsights.Extensibility; + +namespace CacheMeIfYouCan.Redis +{ + internal class RedisTelemetryProcessor : ITelemetryProcessor + { + private readonly IRedisTelemetryConfig _redisTelemetryConfig; + private ITelemetryProcessor Next { get; } + + // next will point to the next RedisTelemetryProcessor in the chain. + public RedisTelemetryProcessor(ITelemetryProcessor next, IRedisTelemetryConfig redisTelemetryConfig) + { + _redisTelemetryConfig = redisTelemetryConfig; + Next = next; + } + + public void Process(ITelemetry item) + { + if (item is DependencyTelemetry request + && request.Duration.TotalMilliseconds > _redisTelemetryConfig?.MillisecondThreshold) + { + Next.Process(item); + } + } + } +} diff --git a/src/CacheMeIfYouCan/CacheMeIfYouCan.csproj b/src/CacheMeIfYouCan/CacheMeIfYouCan.csproj index 6a7b507b..b66f475e 100644 --- a/src/CacheMeIfYouCan/CacheMeIfYouCan.csproj +++ b/src/CacheMeIfYouCan/CacheMeIfYouCan.csproj @@ -6,12 +6,13 @@ High performance, highly configurable caching library + - - + + - + diff --git a/src/CacheMeIfYouCan/DistributedCacheEventsWrapperBase.cs b/src/CacheMeIfYouCan/DistributedCacheEventsWrapperBase.cs index 75e12c26..65536f14 100644 --- a/src/CacheMeIfYouCan/DistributedCacheEventsWrapperBase.cs +++ b/src/CacheMeIfYouCan/DistributedCacheEventsWrapperBase.cs @@ -210,6 +210,11 @@ public async Task TryRemove(TKey key) } } + public void SetTelemetry(IRedisTelemetry redisTelemetry) + { + _innerCache.SetTelemetry(redisTelemetry); + } + protected virtual void OnTryRemoveCompletedSuccessfully( TKey key, bool wasRemoved, @@ -355,6 +360,11 @@ public async Task TryRemove(TOuterKey outerKey, TInnerKey innerKey) } } + public void SetTelemetry(IRedisTelemetry redisTelemetry) + { + _innerCache.SetTelemetry(redisTelemetry); + } + protected virtual void OnTryRemoveCompletedSuccessfully( TOuterKey outerKey, TInnerKey innerKey, diff --git a/src/CacheMeIfYouCan/IDistributedCache.cs b/src/CacheMeIfYouCan/IDistributedCache.cs index 1c2b646f..bc10070c 100644 --- a/src/CacheMeIfYouCan/IDistributedCache.cs +++ b/src/CacheMeIfYouCan/IDistributedCache.cs @@ -16,6 +16,8 @@ public interface IDistributedCache Task SetMany(ReadOnlyMemory> values, TimeSpan timeToLive); Task TryRemove(TKey key); + + void SetTelemetry(IRedisTelemetry redisTelemetry); } public interface IDistributedCache @@ -25,6 +27,8 @@ public interface IDistributedCache Task SetMany(TOuterKey outerKey, ReadOnlyMemory> values, TimeSpan timeToLive); Task TryRemove(TOuterKey outerKey, TInnerKey innerKey); + + void SetTelemetry(IRedisTelemetry redisTelemetry); } public static class IDistributedCacheExtensions @@ -67,5 +71,6 @@ public static Task Set( { return cache.SetMany(outerKey, new[] { new KeyValuePair(innerKey , value) }, timeToLive); } + } } \ No newline at end of file diff --git a/src/CacheMeIfYouCan/IRedisTelemetry.cs b/src/CacheMeIfYouCan/IRedisTelemetry.cs new file mode 100644 index 00000000..f5a323ce --- /dev/null +++ b/src/CacheMeIfYouCan/IRedisTelemetry.cs @@ -0,0 +1,10 @@ +using System; +using System.Threading.Tasks; + +namespace CacheMeIfYouCan +{ + public interface IRedisTelemetry + { + Task CallRedisAsync(Func> func, string command, string key); + } +} \ No newline at end of file diff --git a/test.runsettings b/test.runsettings new file mode 100644 index 00000000..151feab7 --- /dev/null +++ b/test.runsettings @@ -0,0 +1,10 @@ + + + + + + 10.20.25.58 + + + + \ No newline at end of file diff --git a/tests/CacheMeIfYouCan.Redis.Tests/CacheMeIfYouCan.Redis.Tests.csproj b/tests/CacheMeIfYouCan.Redis.Tests/CacheMeIfYouCan.Redis.Tests.csproj index 252db2ef..58c87c05 100644 --- a/tests/CacheMeIfYouCan.Redis.Tests/CacheMeIfYouCan.Redis.Tests.csproj +++ b/tests/CacheMeIfYouCan.Redis.Tests/CacheMeIfYouCan.Redis.Tests.csproj @@ -2,6 +2,7 @@ netcoreapp3.1 false + $(MSBuildProjectDirectory)\test.runsettings diff --git a/tests/CacheMeIfYouCan.Redis.Tests/MockTelemetry.cs b/tests/CacheMeIfYouCan.Redis.Tests/MockTelemetry.cs new file mode 100644 index 00000000..332dc8d7 --- /dev/null +++ b/tests/CacheMeIfYouCan.Redis.Tests/MockTelemetry.cs @@ -0,0 +1,13 @@ +using System; + +namespace CacheMeIfYouCan.Redis.Tests +{ + public class MockTelemetry + { + public string Host { get; set; } + public string Cache { get; set; } + public string Command { get; set; } + public DateTimeOffset Start { get; set; } + public TimeSpan Duration { get; set; } + } +} \ No newline at end of file diff --git a/tests/CacheMeIfYouCan.Redis.Tests/MockTelemetryProcessor.cs b/tests/CacheMeIfYouCan.Redis.Tests/MockTelemetryProcessor.cs new file mode 100644 index 00000000..bf914884 --- /dev/null +++ b/tests/CacheMeIfYouCan.Redis.Tests/MockTelemetryProcessor.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; +using Microsoft.ApplicationInsights.Channel; +using Microsoft.ApplicationInsights.DataContracts; +using Microsoft.ApplicationInsights.Extensibility; + +namespace CacheMeIfYouCan.Redis.Tests +{ + public class MockTelemetryProcessor : ITelemetryProcessor + { + private readonly List _telemetry = new List(); + + public void Process(ITelemetry item) + { + var data = item as DependencyTelemetry; + _telemetry.Add(new + MockTelemetry + { + Host = data?.Target, + Cache = data?.Name, + Command = data?.Data, + Start = data?.Timestamp ?? DateTimeOffset.MinValue, + Duration = data?.Duration ?? TimeSpan.Zero + }); + } + + public List GetTrace() + { + return _telemetry; + } + } + + public class MockTelemetryConfig : IRedisTelemetryConfig + { + public MockTelemetryConfig(int millisecondThreshold) + { + MillisecondThreshold = millisecondThreshold; + } + + public int MillisecondThreshold { get; } + } +} \ No newline at end of file diff --git a/tests/CacheMeIfYouCan.Redis.Tests/RedisCacheTests1.cs b/tests/CacheMeIfYouCan.Redis.Tests/RedisCacheTests1.cs index c777d389..f48a64a2 100644 --- a/tests/CacheMeIfYouCan.Redis.Tests/RedisCacheTests1.cs +++ b/tests/CacheMeIfYouCan.Redis.Tests/RedisCacheTests1.cs @@ -336,7 +336,48 @@ await cache4 keysChangedRemotely1.OrderBy(t => t.Item1).ThenBy(t => t.Item2).Should().BeEquivalentTo(expectedEvents1); keysChangedRemotely2.OrderBy(t => t.Item1).ThenBy(t => t.Item2).Should().BeEquivalentTo(expectedEvents2); } - + + [Theory] + [InlineData(0, true)] + [InlineData(1000, false)] + public async Task TestTelemetryOnCommand(int threshold, bool anyTrace) + { + using var connection = BuildConnection(); + using var cache = BuildRedisCache(connection, useSerializer: false); + + var mockTelemetry = new MockTelemetryProcessor(); + var config = new MockTelemetryConfig(threshold); + + cache.WithAppInsightsTelemetry(mockTelemetry, config, "host", "TestCache1"); + + const int elementsToCache = 10; + + var keys = Enumerable + .Range(1, elementsToCache) + .ToArray(); + + var values = keys + .Select(i => new KeyValuePair(i, new TestClass(i))) + .ToArray(); + + await cache.SetMany(values, TimeSpan.FromSeconds(5)).ConfigureAwait(false); + + Task.WaitAll(); + + var trace = mockTelemetry.GetTrace(); + + trace.Should().NotBeNull(); + + if (anyTrace) + { + trace.Should().NotBeEmpty(); + trace.Count.Should().BeLessOrEqualTo(elementsToCache); + trace.First().Command.Should().Contain("StringSetAsync"); + trace.Last().Command.Should().Contain("StringSetAsync"); + } + } + + private static RedisConnection BuildConnection() => new RedisConnection(TestConnectionString.Value); private static RedisCache BuildRedisCache( diff --git a/tests/CacheMeIfYouCan.Redis.Tests/RedisCacheTests2.cs b/tests/CacheMeIfYouCan.Redis.Tests/RedisCacheTests2.cs index b711af1f..70ae05b7 100644 --- a/tests/CacheMeIfYouCan.Redis.Tests/RedisCacheTests2.cs +++ b/tests/CacheMeIfYouCan.Redis.Tests/RedisCacheTests2.cs @@ -249,7 +249,45 @@ await cache4 keysChangedRemotely1.OrderBy(t => t.Item1).ThenBy(t => t.Item2).Should().BeEquivalentTo(expectedEvents1); keysChangedRemotely2.OrderBy(t => t.Item1).ThenBy(t => t.Item2).Should().BeEquivalentTo(expectedEvents2); } - + + [Theory] + [InlineData(0, true)] + [InlineData(1000, false)] + public async Task TestTelemetryOnCommand(int threshold, bool anyTrace) + { + using var connection = BuildConnection(); + using var cache = BuildRedisCache(connection); + + var mockTelemetry = new MockTelemetryProcessor(); + var mockConfig = new MockTelemetryConfig(threshold); + + cache.WithAppInsightsTelemetry(mockTelemetry, mockConfig, "host", "TestCache2"); + + const int elementsToCache = 10; + + var keys = Enumerable.Range(1, elementsToCache).ToArray(); + var values = keys + .Where(k => k % 2 == 0) + .Select(i => new KeyValuePair(i, new TestClass(i))) + .ToArray(); + + await cache.SetMany(1, values, TimeSpan.FromSeconds(1)); + + Task.WaitAll(); + + var trace = mockTelemetry.GetTrace(); + + trace.Should().NotBeNull(); + + if (anyTrace) + { + trace.Should().NotBeEmpty(); + trace.Count.Should().BeLessOrEqualTo(elementsToCache); + trace.First().Command.Should().Contain("StringSetAsync"); + trace.Last().Command.Should().Contain("StringSetAsync"); + } + } + private static RedisConnection BuildConnection() => new RedisConnection(TestConnectionString.Value); private static RedisCache BuildRedisCache( diff --git a/tests/CacheMeIfYouCan.Tests/MockDistributedCache.cs b/tests/CacheMeIfYouCan.Tests/MockDistributedCache.cs index 1fc7f6ff..3cc350db 100644 --- a/tests/CacheMeIfYouCan.Tests/MockDistributedCache.cs +++ b/tests/CacheMeIfYouCan.Tests/MockDistributedCache.cs @@ -98,6 +98,10 @@ public Task TryRemove(TKey key) return Task.FromResult(_innerCache.TryRemove(key, out _)); } + public void SetTelemetry(IRedisTelemetry redisTelemetry) + { + } + public void ThrowExceptionOnNextAction() => _throwExceptionOnNextAction = true; private void ThrowIfRequested() @@ -180,6 +184,10 @@ public Task TryRemove(TOuterKey outerKey, TInnerKey innerKey) return Task.FromResult(_innerCache.TryRemove(outerKey, innerKey, out _)); } + public void SetTelemetry(IRedisTelemetry redisTelemetry) + { + } + public void ThrowExceptionOnNextAction() => _throwExceptionOnNextAction = true; private void ThrowIfRequested() From 8f4e08b39501e5361f65ef437c528598a479540a Mon Sep 17 00:00:00 2001 From: Nathan Comben Date: Mon, 22 Nov 2021 17:24:56 +0000 Subject: [PATCH 2/3] Rename Redis enhancement interfaces and add usage to readme. --- README.md | 19 +++++++++++++ .../DistributedCachePollyWrapper.cs | 8 +++--- ...hedObjectConfigurationManagerExtensions.cs | 20 ++++++------- .../IRedisTelemetryConfig.cs | 7 ----- src/CacheMeIfYouCan.Redis/RedisCache.cs | 28 +++++++++---------- ...disTelemetry.cs => RedisCacheTelemetry.cs} | 12 ++++---- .../RedisTelemetryProcessor.cs | 8 +++--- .../DistributedCacheEventsWrapperBase.cs | 8 +++--- src/CacheMeIfYouCan/IDistributedCache.cs | 4 +-- .../IDistributedCacheTelemetry.cs | 10 +++++++ src/CacheMeIfYouCan/IRedisTelemetry.cs | 10 ------- src/CacheMeIfYouCan/ITelemetryConfig.cs | 7 +++++ .../MockTelemetryConfig.cs | 12 ++++++++ .../MockTelemetryProcessor.cs | 10 ------- .../RedisCacheTests1.cs | 2 +- .../RedisCacheTests2.cs | 2 +- .../MockDistributedCache.cs | 4 +-- 17 files changed, 96 insertions(+), 75 deletions(-) delete mode 100644 src/CacheMeIfYouCan.Redis/IRedisTelemetryConfig.cs rename src/CacheMeIfYouCan.Redis/{RedisTelemetry.cs => RedisCacheTelemetry.cs} (73%) create mode 100644 src/CacheMeIfYouCan/IDistributedCacheTelemetry.cs delete mode 100644 src/CacheMeIfYouCan/IRedisTelemetry.cs create mode 100644 src/CacheMeIfYouCan/ITelemetryConfig.cs create mode 100644 tests/CacheMeIfYouCan.Redis.Tests/MockTelemetryConfig.cs diff --git a/README.md b/README.md index e097c93c..4816f133 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,25 @@ var cachedFunction = CachedFunctionFactory .Build(); ``` +You can either use an Application Insights telemtry processor or your own to gather performance metrics on the peformance of +distributed cache commands, just implement the ITelemetryProcessor provided by Application Insights. +If you implement an ITelemetryConfig you can provide a threshold so only slower cache calls get logged - useful to help when +diagnosing performance issues with your implementation. +To use, extend the .ConfigureFor call with the .WithApplicationInsightsTelemetry + +```csharp +Func> originalFunction = ... + +var cachedFunction = CachedFunctionFactory + .ConfigureFor(originalFunction) + .WithLocalCache(new DictionaryCache()) + .WithDistributedCache(new RedisCache(...)) + .WithTimeToLive(TimeSpan.FromHours(1)) + .WithApplicationInsightsTelemetry(new MyTelemetryProcessor(), new MyTelemetryConfig(), "myRedisHost", "myCache"); + .OnResult(r => logSuccess(r), ex => logException(ex)) + .Build(); +``` + It is possible to create cached functions where the original function has up to 8 parameters (+ an optional cancellation token) and the cache key can be any function of the input parameters. The original function can be synchronous, asynchronous, or return a `ValueTask` (the underlying implementation uses ValueTasks). The resulting diff --git a/src/CacheMeIfYouCan.Polly/DistributedCachePollyWrapper.cs b/src/CacheMeIfYouCan.Polly/DistributedCachePollyWrapper.cs index 7d1b2c5a..7d31b45d 100644 --- a/src/CacheMeIfYouCan.Polly/DistributedCachePollyWrapper.cs +++ b/src/CacheMeIfYouCan.Polly/DistributedCachePollyWrapper.cs @@ -69,9 +69,9 @@ public Task TryRemove(TKey key) : _tryRemovePolicy.ExecuteAsync(() => _innerCache.TryRemove(key)); } - public void SetTelemetry(IRedisTelemetry redisTelemetry) + public void SetTelemetry(IDistributedCacheTelemetry distributedCacheTelemetry) { - _innerCache.SetTelemetry(redisTelemetry); + _innerCache.SetTelemetry(distributedCacheTelemetry); } } @@ -128,9 +128,9 @@ public Task TryRemove(TOuterKey outerKey, TInnerKey innerKey) : _tryRemovePolicy.ExecuteAsync(() => _innerCache.TryRemove(outerKey, innerKey)); } - public void SetTelemetry(IRedisTelemetry redisTelemetry) + public void SetTelemetry(IDistributedCacheTelemetry distributedCacheTelemetry) { - _innerCache.SetTelemetry(redisTelemetry); + _innerCache.SetTelemetry(distributedCacheTelemetry); } } } \ No newline at end of file diff --git a/src/CacheMeIfYouCan.Redis/CachedObjectConfigurationManagerExtensions.cs b/src/CacheMeIfYouCan.Redis/CachedObjectConfigurationManagerExtensions.cs index e97711ef..edd5578b 100644 --- a/src/CacheMeIfYouCan.Redis/CachedObjectConfigurationManagerExtensions.cs +++ b/src/CacheMeIfYouCan.Redis/CachedObjectConfigurationManagerExtensions.cs @@ -4,20 +4,20 @@ namespace CacheMeIfYouCan.Redis { public static class CachedObjectConfigurationManagerExtensions { - public static IDistributedCache WithAppInsightsTelemetry(this IDistributedCache redisCache, - ITelemetryProcessor telemetryProcessor, IRedisTelemetryConfig redisTelemetryConfig, string hostName, string cacheName) + public static IDistributedCache WithApplicationInsightsTelemetry(this IDistributedCache distributedCache, + ITelemetryProcessor telemetryProcessor, ITelemetryConfig telemetryConfig, string hostName, string cacheName) { - var redisTelemetry = new RedisTelemetry(telemetryProcessor, redisTelemetryConfig, hostName, cacheName); - redisCache.SetTelemetry(redisTelemetry); - return redisCache; + var redisTelemetry = new RedisCacheTelemetry(telemetryProcessor, telemetryConfig, hostName, cacheName); + distributedCache.SetTelemetry(redisTelemetry); + return distributedCache; } - public static IDistributedCache WithAppInsightsTelemetry(this IDistributedCache redisCache, - ITelemetryProcessor telemetryProcessor, IRedisTelemetryConfig redisTelemetryConfig, string hostName, string cacheName) + public static IDistributedCache WithApplicationInsightsTelemetry(this IDistributedCache distributedCache, + ITelemetryProcessor telemetryProcessor, ITelemetryConfig telemetryConfig, string hostName, string cacheName) { - var redisTelemetry = new RedisTelemetry(telemetryProcessor, redisTelemetryConfig, hostName, cacheName); - redisCache.SetTelemetry(redisTelemetry); - return redisCache; + var redisTelemetry = new RedisCacheTelemetry(telemetryProcessor, telemetryConfig, hostName, cacheName); + distributedCache.SetTelemetry(redisTelemetry); + return distributedCache; } } } diff --git a/src/CacheMeIfYouCan.Redis/IRedisTelemetryConfig.cs b/src/CacheMeIfYouCan.Redis/IRedisTelemetryConfig.cs deleted file mode 100644 index b47f990e..00000000 --- a/src/CacheMeIfYouCan.Redis/IRedisTelemetryConfig.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace CacheMeIfYouCan.Redis -{ - public interface IRedisTelemetryConfig - { - public int MillisecondThreshold { get; } - } -} \ No newline at end of file diff --git a/src/CacheMeIfYouCan.Redis/RedisCache.cs b/src/CacheMeIfYouCan.Redis/RedisCache.cs index 8ef17aba..748b5cc3 100644 --- a/src/CacheMeIfYouCan.Redis/RedisCache.cs +++ b/src/CacheMeIfYouCan.Redis/RedisCache.cs @@ -26,11 +26,11 @@ public sealed class RedisCache : IDistributedCache, private readonly RecentlySetOrRemovedKeysManager _recentlyRemovedKeysManager; private readonly int _keyPrefixLength; private bool _disposed; - private IRedisTelemetry _redisTelemetry = new NoTelemetry(); + private IDistributedCacheTelemetry _distributedCacheTelemetry = new NoTelemetry(); - public void SetTelemetry(IRedisTelemetry redisTelemetry) + public void SetTelemetry(IDistributedCacheTelemetry distributedCacheTelemetry) { - _redisTelemetry = redisTelemetry; + _distributedCacheTelemetry = distributedCacheTelemetry; } public RedisCache( @@ -130,7 +130,7 @@ private RedisCache( var redisKey = _keySerializer(key); - var fromRedis = await _redisTelemetry.CallRedisAsync( + var fromRedis = await _distributedCacheTelemetry.CallAsync( () => GetDatabase().StringGetWithExpiryAsync(redisKey), "StringGetWithExpiryAsync", redisKey) .ConfigureAwait(false); @@ -156,7 +156,7 @@ public async Task Set(TKey key, TValue value, TimeSpan timeToLive) _recentlySetKeysManager?.Mark(((string)redisKey).Substring(_keyPrefixLength)); - var task = _redisTelemetry.CallRedisAsync( + var task = _distributedCacheTelemetry.CallAsync( () => GetDatabase().StringSetAsync(redisKey, redisValue, timeToLive, flags: _setValueFlags), "StringSetAsync", redisKey); @@ -209,7 +209,7 @@ await Task async Task<(TKey, RedisValueWithExpiry)> GetSingle(TKey key) { var redisKey = _keySerializer(key); - var fromRedis = await _redisTelemetry.CallRedisAsync( + var fromRedis = await _distributedCacheTelemetry.CallAsync( async () => await GetDatabase() .StringGetWithExpiryAsync(redisKey) .ConfigureAwait(false), "StringGetWithExpiryAsync", redisKey) @@ -291,7 +291,7 @@ IReadOnlyCollection CreateTasks() _recentlySetKeysManager?.Mark(((string)redisKey).Substring(_keyPrefixLength)); - pooledTasksArray[index++] = _redisTelemetry.CallRedisAsync( + pooledTasksArray[index++] = _distributedCacheTelemetry.CallAsync( () => GetDatabase().StringSetAsync(redisKey, redisValue, timeToLive, flags: _setValueFlags), "StringSetAsync", redisKey); } @@ -308,7 +308,7 @@ public async Task TryRemove(TKey key) _recentlyRemovedKeysManager?.Mark(((string)redisKey).Substring(_keyPrefixLength)); - return await _redisTelemetry.CallRedisAsync( + return await _distributedCacheTelemetry.CallAsync( () => GetDatabase().KeyDeleteAsync(redisKey), "KeyDeleteAsync", redisKey) .ConfigureAwait(false); @@ -370,11 +370,11 @@ public sealed class RedisCache : IDistributedCache private readonly RecentlySetOrRemovedKeysManager _recentlyRemovedKeysManager; private readonly int _keyPrefixLength; private bool _disposed; - private IRedisTelemetry _redisTelemetry = new NoTelemetry(); + private IDistributedCacheTelemetry _distributedCacheTelemetry = new NoTelemetry(); - public void SetTelemetry(IRedisTelemetry redisTelemetry) + public void SetTelemetry(IDistributedCacheTelemetry distributedCacheTelemetry) { - _redisTelemetry = redisTelemetry; + _distributedCacheTelemetry = distributedCacheTelemetry; } public RedisCache( @@ -521,7 +521,7 @@ await Task { var redisKey = redisKeyPrefix.Append(_innerKeySerializer(innerKey)); - var fromRedis = await _redisTelemetry.CallRedisAsync( + var fromRedis = await _distributedCacheTelemetry.CallAsync( async () => await GetDatabase() .StringGetWithExpiryAsync(redisKey) .ConfigureAwait(false), "StringGetWithExpiryAsync", redisKey) @@ -611,7 +611,7 @@ IReadOnlyCollection CreateTasks() _recentlySetKeysManager?.Mark(((string)redisKey).Substring(_keyPrefixLength)); - pooledTasksArray[index++] = _redisTelemetry.CallRedisAsync( + pooledTasksArray[index++] = _distributedCacheTelemetry.CallAsync( () => GetDatabase().StringSetAsync(redisKey, redisValue, timeToLive, flags: _setValueFlags), "StringSetAsync", redisKey); } @@ -630,7 +630,7 @@ public async Task TryRemove(TOuterKey outerKey, TInnerKey innerKey) _recentlyRemovedKeysManager?.Mark(((string)redisKey).Substring(_keyPrefixLength)); - return await _redisTelemetry.CallRedisAsync( + return await _distributedCacheTelemetry.CallAsync( async () => await GetDatabase() .KeyDeleteAsync(redisKey) .ConfigureAwait(false), diff --git a/src/CacheMeIfYouCan.Redis/RedisTelemetry.cs b/src/CacheMeIfYouCan.Redis/RedisCacheTelemetry.cs similarity index 73% rename from src/CacheMeIfYouCan.Redis/RedisTelemetry.cs rename to src/CacheMeIfYouCan.Redis/RedisCacheTelemetry.cs index fdd58819..8ea1802d 100644 --- a/src/CacheMeIfYouCan.Redis/RedisTelemetry.cs +++ b/src/CacheMeIfYouCan.Redis/RedisCacheTelemetry.cs @@ -5,20 +5,20 @@ namespace CacheMeIfYouCan.Redis { - internal class RedisTelemetry : IRedisTelemetry + internal class RedisCacheTelemetry : IDistributedCacheTelemetry { private readonly ITelemetryProcessor _telemetryProcessor; private readonly string _host; private readonly string _cacheName; - public RedisTelemetry(ITelemetryProcessor telemetryProcessor, IRedisTelemetryConfig redisTelemetryConfig, string host, string cacheName) + public RedisCacheTelemetry(ITelemetryProcessor telemetryProcessor, ITelemetryConfig telemetryConfig, string host, string cacheName) { - _telemetryProcessor = new RedisTelemetryProcessor(telemetryProcessor, redisTelemetryConfig); + _telemetryProcessor = new RedisTelemetryProcessor(telemetryProcessor, telemetryConfig); _host = host; _cacheName = cacheName; } - public async Task CallRedisAsync(Func> func, string command, string key) + public async Task CallAsync(Func> func, string command, string key) { var success = false; var commandInfoText = $"Command {command}{Environment.NewLine}Key '{key}'"; @@ -43,9 +43,9 @@ public async Task CallRedisAsync(Func> func, string command, strin } } - internal class NoTelemetry : IRedisTelemetry + internal class NoTelemetry : IDistributedCacheTelemetry { - public async Task CallRedisAsync(Func> func, string command, string key) + public async Task CallAsync(Func> func, string command, string key) { return await func().ConfigureAwait(false); } diff --git a/src/CacheMeIfYouCan.Redis/RedisTelemetryProcessor.cs b/src/CacheMeIfYouCan.Redis/RedisTelemetryProcessor.cs index 2e4a4626..ec4e5942 100644 --- a/src/CacheMeIfYouCan.Redis/RedisTelemetryProcessor.cs +++ b/src/CacheMeIfYouCan.Redis/RedisTelemetryProcessor.cs @@ -6,20 +6,20 @@ namespace CacheMeIfYouCan.Redis { internal class RedisTelemetryProcessor : ITelemetryProcessor { - private readonly IRedisTelemetryConfig _redisTelemetryConfig; + private readonly ITelemetryConfig _telemetryConfig; private ITelemetryProcessor Next { get; } // next will point to the next RedisTelemetryProcessor in the chain. - public RedisTelemetryProcessor(ITelemetryProcessor next, IRedisTelemetryConfig redisTelemetryConfig) + public RedisTelemetryProcessor(ITelemetryProcessor next, ITelemetryConfig telemetryConfig) { - _redisTelemetryConfig = redisTelemetryConfig; + _telemetryConfig = telemetryConfig; Next = next; } public void Process(ITelemetry item) { if (item is DependencyTelemetry request - && request.Duration.TotalMilliseconds > _redisTelemetryConfig?.MillisecondThreshold) + && request.Duration.TotalMilliseconds > _telemetryConfig?.MillisecondThreshold) { Next.Process(item); } diff --git a/src/CacheMeIfYouCan/DistributedCacheEventsWrapperBase.cs b/src/CacheMeIfYouCan/DistributedCacheEventsWrapperBase.cs index 65536f14..a2f191ef 100644 --- a/src/CacheMeIfYouCan/DistributedCacheEventsWrapperBase.cs +++ b/src/CacheMeIfYouCan/DistributedCacheEventsWrapperBase.cs @@ -210,9 +210,9 @@ public async Task TryRemove(TKey key) } } - public void SetTelemetry(IRedisTelemetry redisTelemetry) + public void SetTelemetry(IDistributedCacheTelemetry distributedCacheTelemetry) { - _innerCache.SetTelemetry(redisTelemetry); + _innerCache.SetTelemetry(distributedCacheTelemetry); } protected virtual void OnTryRemoveCompletedSuccessfully( @@ -360,9 +360,9 @@ public async Task TryRemove(TOuterKey outerKey, TInnerKey innerKey) } } - public void SetTelemetry(IRedisTelemetry redisTelemetry) + public void SetTelemetry(IDistributedCacheTelemetry distributedCacheTelemetry) { - _innerCache.SetTelemetry(redisTelemetry); + _innerCache.SetTelemetry(distributedCacheTelemetry); } protected virtual void OnTryRemoveCompletedSuccessfully( diff --git a/src/CacheMeIfYouCan/IDistributedCache.cs b/src/CacheMeIfYouCan/IDistributedCache.cs index bc10070c..71944d78 100644 --- a/src/CacheMeIfYouCan/IDistributedCache.cs +++ b/src/CacheMeIfYouCan/IDistributedCache.cs @@ -17,7 +17,7 @@ public interface IDistributedCache Task TryRemove(TKey key); - void SetTelemetry(IRedisTelemetry redisTelemetry); + void SetTelemetry(IDistributedCacheTelemetry distributedCacheTelemetry); } public interface IDistributedCache @@ -28,7 +28,7 @@ public interface IDistributedCache Task TryRemove(TOuterKey outerKey, TInnerKey innerKey); - void SetTelemetry(IRedisTelemetry redisTelemetry); + void SetTelemetry(IDistributedCacheTelemetry distributedCacheTelemetry); } public static class IDistributedCacheExtensions diff --git a/src/CacheMeIfYouCan/IDistributedCacheTelemetry.cs b/src/CacheMeIfYouCan/IDistributedCacheTelemetry.cs new file mode 100644 index 00000000..1e04a327 --- /dev/null +++ b/src/CacheMeIfYouCan/IDistributedCacheTelemetry.cs @@ -0,0 +1,10 @@ +using System; +using System.Threading.Tasks; + +namespace CacheMeIfYouCan +{ + public interface IDistributedCacheTelemetry + { + Task CallAsync(Func> func, string command, string key); + } +} \ No newline at end of file diff --git a/src/CacheMeIfYouCan/IRedisTelemetry.cs b/src/CacheMeIfYouCan/IRedisTelemetry.cs deleted file mode 100644 index f5a323ce..00000000 --- a/src/CacheMeIfYouCan/IRedisTelemetry.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; -using System.Threading.Tasks; - -namespace CacheMeIfYouCan -{ - public interface IRedisTelemetry - { - Task CallRedisAsync(Func> func, string command, string key); - } -} \ No newline at end of file diff --git a/src/CacheMeIfYouCan/ITelemetryConfig.cs b/src/CacheMeIfYouCan/ITelemetryConfig.cs new file mode 100644 index 00000000..7cee6e46 --- /dev/null +++ b/src/CacheMeIfYouCan/ITelemetryConfig.cs @@ -0,0 +1,7 @@ +namespace CacheMeIfYouCan +{ + public interface ITelemetryConfig + { + public int MillisecondThreshold { get; } + } +} \ No newline at end of file diff --git a/tests/CacheMeIfYouCan.Redis.Tests/MockTelemetryConfig.cs b/tests/CacheMeIfYouCan.Redis.Tests/MockTelemetryConfig.cs new file mode 100644 index 00000000..c1447064 --- /dev/null +++ b/tests/CacheMeIfYouCan.Redis.Tests/MockTelemetryConfig.cs @@ -0,0 +1,12 @@ +namespace CacheMeIfYouCan.Redis.Tests +{ + public class MockTelemetryConfig : ITelemetryConfig + { + public MockTelemetryConfig(int millisecondThreshold) + { + MillisecondThreshold = millisecondThreshold; + } + + public int MillisecondThreshold { get; } + } +} \ No newline at end of file diff --git a/tests/CacheMeIfYouCan.Redis.Tests/MockTelemetryProcessor.cs b/tests/CacheMeIfYouCan.Redis.Tests/MockTelemetryProcessor.cs index bf914884..5b35f54f 100644 --- a/tests/CacheMeIfYouCan.Redis.Tests/MockTelemetryProcessor.cs +++ b/tests/CacheMeIfYouCan.Redis.Tests/MockTelemetryProcessor.cs @@ -29,14 +29,4 @@ public List GetTrace() return _telemetry; } } - - public class MockTelemetryConfig : IRedisTelemetryConfig - { - public MockTelemetryConfig(int millisecondThreshold) - { - MillisecondThreshold = millisecondThreshold; - } - - public int MillisecondThreshold { get; } - } } \ No newline at end of file diff --git a/tests/CacheMeIfYouCan.Redis.Tests/RedisCacheTests1.cs b/tests/CacheMeIfYouCan.Redis.Tests/RedisCacheTests1.cs index f48a64a2..47a69083 100644 --- a/tests/CacheMeIfYouCan.Redis.Tests/RedisCacheTests1.cs +++ b/tests/CacheMeIfYouCan.Redis.Tests/RedisCacheTests1.cs @@ -348,7 +348,7 @@ public async Task TestTelemetryOnCommand(int threshold, bool anyTrace) var mockTelemetry = new MockTelemetryProcessor(); var config = new MockTelemetryConfig(threshold); - cache.WithAppInsightsTelemetry(mockTelemetry, config, "host", "TestCache1"); + cache.WithApplicationInsightsTelemetry(mockTelemetry, config, "host", "TestCache1"); const int elementsToCache = 10; diff --git a/tests/CacheMeIfYouCan.Redis.Tests/RedisCacheTests2.cs b/tests/CacheMeIfYouCan.Redis.Tests/RedisCacheTests2.cs index 70ae05b7..ae05801f 100644 --- a/tests/CacheMeIfYouCan.Redis.Tests/RedisCacheTests2.cs +++ b/tests/CacheMeIfYouCan.Redis.Tests/RedisCacheTests2.cs @@ -261,7 +261,7 @@ public async Task TestTelemetryOnCommand(int threshold, bool anyTrace) var mockTelemetry = new MockTelemetryProcessor(); var mockConfig = new MockTelemetryConfig(threshold); - cache.WithAppInsightsTelemetry(mockTelemetry, mockConfig, "host", "TestCache2"); + cache.WithApplicationInsightsTelemetry(mockTelemetry, mockConfig, "host", "TestCache2"); const int elementsToCache = 10; diff --git a/tests/CacheMeIfYouCan.Tests/MockDistributedCache.cs b/tests/CacheMeIfYouCan.Tests/MockDistributedCache.cs index 3cc350db..dd0d075f 100644 --- a/tests/CacheMeIfYouCan.Tests/MockDistributedCache.cs +++ b/tests/CacheMeIfYouCan.Tests/MockDistributedCache.cs @@ -98,7 +98,7 @@ public Task TryRemove(TKey key) return Task.FromResult(_innerCache.TryRemove(key, out _)); } - public void SetTelemetry(IRedisTelemetry redisTelemetry) + public void SetTelemetry(IDistributedCacheTelemetry distributedCacheTelemetry) { } @@ -184,7 +184,7 @@ public Task TryRemove(TOuterKey outerKey, TInnerKey innerKey) return Task.FromResult(_innerCache.TryRemove(outerKey, innerKey, out _)); } - public void SetTelemetry(IRedisTelemetry redisTelemetry) + public void SetTelemetry(IDistributedCacheTelemetry distributedCacheTelemetry) { } From 12de9088207ecfe4f5b767e338bc6459e9933636 Mon Sep 17 00:00:00 2001 From: Nathan Comben Date: Wed, 1 Dec 2021 10:01:13 +0000 Subject: [PATCH 3/3] Move telemetry implementation to wrapper class to minimise impact of new code - changes telemetry to set commands but that's okay. Update configuration and improve tests. --- .../DistributedCachePollyWrapper.cs | 10 - .../CacheMeIfYouCan.Redis.csproj | 3 - ...hedObjectConfigurationManagerExtensions.cs | 24 ++- ...tributedCacheApplicationInsightsWrapper.cs | 186 ++++++++++++++++++ src/CacheMeIfYouCan.Redis/RedisCache.cs | 173 ++++++++-------- .../RedisCacheTelemetry.cs | 53 ----- .../TelemetryProcessor.cs | 34 ++++ .../DistributedCacheEventsWrapperBase.cs | 11 -- src/CacheMeIfYouCan/IDistributedCache.cs | 4 - .../IDistributedCacheConfig.cs | 9 + .../IDistributedCacheTelemetry.cs | 10 - src/CacheMeIfYouCan/ITelemetryConfig.cs | 7 +- .../DistributedCacheConfig.cs | 9 + .../MockTelemetryConfig.cs | 12 -- .../RedisCacheTests1.cs | 63 +++++- .../RedisCacheTests2.cs | 67 ++++++- .../TelemetryConfig.cs | 7 + ...etryProcessor.cs => TelemetryProcessor.cs} | 2 +- .../MockDistributedCache.cs | 8 - 19 files changed, 458 insertions(+), 234 deletions(-) create mode 100644 src/CacheMeIfYouCan.Redis/DistributedCacheApplicationInsightsWrapper.cs delete mode 100644 src/CacheMeIfYouCan.Redis/RedisCacheTelemetry.cs create mode 100644 src/CacheMeIfYouCan.Redis/TelemetryProcessor.cs create mode 100644 src/CacheMeIfYouCan/IDistributedCacheConfig.cs delete mode 100644 src/CacheMeIfYouCan/IDistributedCacheTelemetry.cs create mode 100644 tests/CacheMeIfYouCan.Redis.Tests/DistributedCacheConfig.cs delete mode 100644 tests/CacheMeIfYouCan.Redis.Tests/MockTelemetryConfig.cs create mode 100644 tests/CacheMeIfYouCan.Redis.Tests/TelemetryConfig.cs rename tests/CacheMeIfYouCan.Redis.Tests/{MockTelemetryProcessor.cs => TelemetryProcessor.cs} (93%) diff --git a/src/CacheMeIfYouCan.Polly/DistributedCachePollyWrapper.cs b/src/CacheMeIfYouCan.Polly/DistributedCachePollyWrapper.cs index 7d31b45d..8fd54bc4 100644 --- a/src/CacheMeIfYouCan.Polly/DistributedCachePollyWrapper.cs +++ b/src/CacheMeIfYouCan.Polly/DistributedCachePollyWrapper.cs @@ -68,11 +68,6 @@ public Task TryRemove(TKey key) ? _innerCache.TryRemove(key) : _tryRemovePolicy.ExecuteAsync(() => _innerCache.TryRemove(key)); } - - public void SetTelemetry(IDistributedCacheTelemetry distributedCacheTelemetry) - { - _innerCache.SetTelemetry(distributedCacheTelemetry); - } } public sealed class DistributedCachePollyWrapper : @@ -127,10 +122,5 @@ public Task TryRemove(TOuterKey outerKey, TInnerKey innerKey) ? _innerCache.TryRemove(outerKey, innerKey) : _tryRemovePolicy.ExecuteAsync(() => _innerCache.TryRemove(outerKey, innerKey)); } - - public void SetTelemetry(IDistributedCacheTelemetry distributedCacheTelemetry) - { - _innerCache.SetTelemetry(distributedCacheTelemetry); - } } } \ No newline at end of file diff --git a/src/CacheMeIfYouCan.Redis/CacheMeIfYouCan.Redis.csproj b/src/CacheMeIfYouCan.Redis/CacheMeIfYouCan.Redis.csproj index 53ca7eeb..06ca5160 100644 --- a/src/CacheMeIfYouCan.Redis/CacheMeIfYouCan.Redis.csproj +++ b/src/CacheMeIfYouCan.Redis/CacheMeIfYouCan.Redis.csproj @@ -14,7 +14,4 @@ - - - diff --git a/src/CacheMeIfYouCan.Redis/CachedObjectConfigurationManagerExtensions.cs b/src/CacheMeIfYouCan.Redis/CachedObjectConfigurationManagerExtensions.cs index edd5578b..03d25041 100644 --- a/src/CacheMeIfYouCan.Redis/CachedObjectConfigurationManagerExtensions.cs +++ b/src/CacheMeIfYouCan.Redis/CachedObjectConfigurationManagerExtensions.cs @@ -4,20 +4,24 @@ namespace CacheMeIfYouCan.Redis { public static class CachedObjectConfigurationManagerExtensions { - public static IDistributedCache WithApplicationInsightsTelemetry(this IDistributedCache distributedCache, - ITelemetryProcessor telemetryProcessor, ITelemetryConfig telemetryConfig, string hostName, string cacheName) + public static IDistributedCache WithApplicationInsightsTelemetry( + this IDistributedCache distributedCache, + IDistributedCacheConfig distributedCacheConfig, + ITelemetryProcessor telemetryProcessor, + ITelemetryConfig telemetryConfig) { - var redisTelemetry = new RedisCacheTelemetry(telemetryProcessor, telemetryConfig, hostName, cacheName); - distributedCache.SetTelemetry(redisTelemetry); - return distributedCache; + return new DistributedCacheApplicationInsightsWrapper(distributedCache, + distributedCacheConfig, telemetryProcessor, telemetryConfig); } - public static IDistributedCache WithApplicationInsightsTelemetry(this IDistributedCache distributedCache, - ITelemetryProcessor telemetryProcessor, ITelemetryConfig telemetryConfig, string hostName, string cacheName) + public static IDistributedCache WithApplicationInsightsTelemetry( + this IDistributedCache distributedCache, + IDistributedCacheConfig distributedCacheConfig, + ITelemetryProcessor telemetryProcessor, + ITelemetryConfig telemetryConfig) { - var redisTelemetry = new RedisCacheTelemetry(telemetryProcessor, telemetryConfig, hostName, cacheName); - distributedCache.SetTelemetry(redisTelemetry); - return distributedCache; + return new DistributedCacheApplicationInsightsWrapper(distributedCache, + distributedCacheConfig, telemetryProcessor, telemetryConfig); } } } diff --git a/src/CacheMeIfYouCan.Redis/DistributedCacheApplicationInsightsWrapper.cs b/src/CacheMeIfYouCan.Redis/DistributedCacheApplicationInsightsWrapper.cs new file mode 100644 index 00000000..4965b246 --- /dev/null +++ b/src/CacheMeIfYouCan.Redis/DistributedCacheApplicationInsightsWrapper.cs @@ -0,0 +1,186 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.ApplicationInsights.Extensibility; + +namespace CacheMeIfYouCan.Redis +{ + public sealed class DistributedCacheApplicationInsightsWrapper + : DistributedCacheEventsWrapperBase + { + private readonly TelemetryProcessor _telemetryProcessor; + + public DistributedCacheApplicationInsightsWrapper(IDistributedCache innerCache, + IDistributedCacheConfig cacheConfig, + ITelemetryProcessor telemetryProcessor, + ITelemetryConfig telemetryConfig) + : base(innerCache) + { + _telemetryProcessor = new TelemetryProcessor(cacheConfig, telemetryProcessor, telemetryConfig); + } + + protected override void OnTryGetCompletedSuccessfully(TKey key, bool resultSuccess, + ValueAndTimeToLive resultValue, TimeSpan duration) + { + _telemetryProcessor.Add(duration, "StringGetWithExpiryAsync", $"Key '{key}'", true); + + base.OnTryGetCompletedSuccessfully(key, resultSuccess, resultValue, duration); + } + + protected override void OnSetManyCompletedSuccessfully(ReadOnlySpan> values, + TimeSpan timeToLive, TimeSpan duration) + { + var keys = $"Keys '{string.Join(",", values.ToArray().Select(d => d.Key))}'"; + + _telemetryProcessor.Add(duration, "StringSetAsync", keys, true); + + base.OnSetManyCompletedSuccessfully(values, timeToLive, duration); + } + + protected override void OnGetManyCompletedSuccessfully(ReadOnlySpan keys, + ReadOnlySpan>> values, TimeSpan duration) + { + var keysText = $"Keys '{string.Join(",", keys.ToArray())}'"; + + _telemetryProcessor.Add(duration, "StringGetWithExpiryAsync", keysText, true); + + base.OnGetManyCompletedSuccessfully(keys, values, duration); + } + + protected override void OnGetManyException(ReadOnlySpan keys, TimeSpan duration, Exception exception, + out bool exceptionHandled) + { + var keysText = $"Keys '{string.Join(",", keys.ToArray())}'"; + + _telemetryProcessor.Add(duration, "StringGetWithExpiryAsync", keysText, false); + + base.OnGetManyException(keys, duration, exception, out exceptionHandled); + } + + protected override void OnSetCompletedSuccessfully(TKey key, TValue value, TimeSpan timeToLive, + TimeSpan duration) + { + _telemetryProcessor.Add(duration, "StringSetAsync", $"Key '{key}'", true); + + base.OnSetCompletedSuccessfully(key, value, timeToLive, duration); + } + + protected override void OnSetException(TKey key, TValue value, TimeSpan timeToLive, TimeSpan duration, + Exception exception, + out bool exceptionHandled) + { + _telemetryProcessor.Add(duration, "StringSetAsync", $"Key '{key}'", false); + + base.OnSetException(key, value, timeToLive, duration, exception, out exceptionHandled); + } + + protected override void OnSetManyException(ReadOnlySpan> values, TimeSpan timeToLive, + TimeSpan duration, Exception exception, + out bool exceptionHandled) + { + var keys = $"Keys '{string.Join(",", values.ToArray().Select(d => d.Key))}'"; + + _telemetryProcessor.Add(duration, "StringSetAsync", keys, false); + + base.OnSetManyException(values, timeToLive, duration, exception, out exceptionHandled); + } + + protected override void OnTryGetException(TKey key, TimeSpan duration, Exception exception, + out bool exceptionHandled) + { + _telemetryProcessor.Add(duration, "StringGetWithExpiryAsync", $"Key '{key}'", false); + + base.OnTryGetException(key, duration, exception, out exceptionHandled); + } + + protected override void OnTryRemoveCompletedSuccessfully(TKey key, bool wasRemoved, TimeSpan duration) + { + _telemetryProcessor.Add(duration, "KeyDeleteAsync", $"Key '{key}'", true); + + base.OnTryRemoveCompletedSuccessfully(key, wasRemoved, duration); + } + + protected override void OnTryRemoveException(TKey key, TimeSpan duration, Exception exception, + out bool exceptionHandled) + { + _telemetryProcessor.Add(duration, "KeyDeleteAsync", $"Key '{key}'", false); + + base.OnTryRemoveException(key, duration, exception, out exceptionHandled); + } + } + + public sealed class DistributedCacheApplicationInsightsWrapper + : DistributedCacheEventsWrapperBase + { + private readonly TelemetryProcessor _telemetryProcessor; + + public DistributedCacheApplicationInsightsWrapper(IDistributedCache innerCache, + IDistributedCacheConfig cacheConfig, + ITelemetryProcessor telemetryProcessor, + ITelemetryConfig telemetryConfig) : base(innerCache) + { + _telemetryProcessor = new TelemetryProcessor(cacheConfig, telemetryProcessor, telemetryConfig); + } + + protected override void OnSetManyException(TOuterKey outerKey, + ReadOnlySpan> values, TimeSpan timeToLive, TimeSpan duration, + Exception exception, out bool exceptionHandled) + { + var keys = + $"Keys {string.Join(",", values.ToArray().Select(innerKeyValue => $"'{outerKey}.{innerKeyValue.Key}'"))}"; + + _telemetryProcessor.Add(duration, "StringSetAsync", keys, false); + + base.OnSetManyException(outerKey, values, timeToLive, duration, exception, out exceptionHandled); + } + + protected override void OnTryRemoveCompletedSuccessfully(TOuterKey outerKey, TInnerKey innerKey, + bool wasRemoved, TimeSpan duration) + { + _telemetryProcessor.Add(duration, "KeyDeleteAsync", $"Key '{outerKey}.{innerKey}'", true); + + base.OnTryRemoveCompletedSuccessfully(outerKey, innerKey, wasRemoved, duration); + } + + protected override void OnTryRemoveException(TOuterKey outerKey, TInnerKey innerKey, TimeSpan duration, + Exception exception, + out bool exceptionHandled) + { + _telemetryProcessor.Add(duration, "KeyDeleteAsync", $"Key '{outerKey}.{innerKey}'", false); + + base.OnTryRemoveException(outerKey, innerKey, duration, exception, out exceptionHandled); + } + + protected override void OnGetManyException(TOuterKey outerKey, ReadOnlySpan innerKeys, + TimeSpan duration, Exception exception, + out bool exceptionHandled) + { + var keys = $"Keys {string.Join(",", innerKeys.ToArray().Select(innerKey => $"'{outerKey}.{innerKey}'"))}"; + + _telemetryProcessor.Add(duration, "StringGetWithExpiryAsync", keys, false); + + base.OnGetManyException(outerKey, innerKeys, duration, exception, out exceptionHandled); + } + + protected override void OnSetManyCompletedSuccessfully(TOuterKey outerKey, + ReadOnlySpan> values, TimeSpan timeToLive, TimeSpan duration) + { + var keys = + $"Keys {string.Join(",", values.ToArray().Select(innerKeyValue => $"'{outerKey}.{innerKeyValue.Key}'"))}"; + + _telemetryProcessor.Add(duration, "StringSetAsync", keys, true); + + base.OnSetManyCompletedSuccessfully(outerKey, values, timeToLive, duration); + } + + protected override void OnGetManyCompletedSuccessfully(TOuterKey outerKey, ReadOnlySpan innerKeys, + ReadOnlySpan>> values, TimeSpan duration) + { + var keys = $"Keys {string.Join(",", innerKeys.ToArray().Select(innerKey => $"'{outerKey}.{innerKey}'"))}"; + + _telemetryProcessor.Add(duration, "StringGetWithExpiryAsync", keys, true); + + base.OnGetManyCompletedSuccessfully(outerKey, innerKeys, values, duration); + } + } +} diff --git a/src/CacheMeIfYouCan.Redis/RedisCache.cs b/src/CacheMeIfYouCan.Redis/RedisCache.cs index 748b5cc3..c714eaed 100644 --- a/src/CacheMeIfYouCan.Redis/RedisCache.cs +++ b/src/CacheMeIfYouCan.Redis/RedisCache.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Buffers; using System.Collections.Generic; using System.IO; @@ -8,10 +8,10 @@ using System.Threading; using System.Threading.Tasks; using CacheMeIfYouCan.Redis.Internal; - using Microsoft.IO; +using Microsoft.IO; using StackExchange.Redis; - namespace CacheMeIfYouCan.Redis +namespace CacheMeIfYouCan.Redis { public sealed class RedisCache : IDistributedCache, IDisposable { @@ -26,12 +26,6 @@ public sealed class RedisCache : IDistributedCache, private readonly RecentlySetOrRemovedKeysManager _recentlyRemovedKeysManager; private readonly int _keyPrefixLength; private bool _disposed; - private IDistributedCacheTelemetry _distributedCacheTelemetry = new NoTelemetry(); - - public void SetTelemetry(IDistributedCacheTelemetry distributedCacheTelemetry) - { - _distributedCacheTelemetry = distributedCacheTelemetry; - } public RedisCache( IRedisConnection connection, @@ -57,7 +51,7 @@ public RedisCache( valueSerializer, recyclableMemoryStreamManager, nullValue); - + _serializeValuesToStreams = true; } @@ -85,10 +79,10 @@ public RedisCache( valueSerializer, valueDeserializer, nullValue); - + _serializeValuesToStreams = false; } - + private RedisCache( IRedisConnection connection, Func keySerializer, @@ -112,14 +106,14 @@ private RedisCache( { if (subscriber is null) throw new ArgumentNullException(nameof(subscriber)); - + _keysChangedRemotely = new Subject<(string, KeyEventType)>(); - + if (keyEventTypesToSubscribeTo.HasFlag(KeyEventType.Set)) _recentlySetKeysManager = new RecentlySetOrRemovedKeysManager(); - + _recentlyRemovedKeysManager = new RecentlySetOrRemovedKeysManager(); - + subscriber.SubscribeToKeyChanges(dbIndex, keyEventTypesToSubscribeTo, OnKeyChanged, keyPrefix); } } @@ -128,11 +122,12 @@ private RedisCache( { CheckDisposed(); + var redisDb = GetDatabase(); + var redisKey = _keySerializer(key); - var fromRedis = await _distributedCacheTelemetry.CallAsync( - () => GetDatabase().StringGetWithExpiryAsync(redisKey), - "StringGetWithExpiryAsync", redisKey) + var fromRedis = await redisDb + .StringGetWithExpiryAsync(redisKey) .ConfigureAwait(false); if (fromRedis.Value.IsNull) @@ -147,6 +142,8 @@ public async Task Set(TKey key, TValue value, TimeSpan timeToLive) { CheckDisposed(); + var redisDb = GetDatabase(); + var redisKey = _keySerializer(key); MemoryStream stream = null; @@ -155,11 +152,8 @@ public async Task Set(TKey key, TValue value, TimeSpan timeToLive) var redisValue = _redisValueConverter.ConvertToRedisValue(value, out stream); _recentlySetKeysManager?.Mark(((string)redisKey).Substring(_keyPrefixLength)); - - var task = _distributedCacheTelemetry.CallAsync( - () => GetDatabase().StringSetAsync(redisKey, redisValue, timeToLive, flags: _setValueFlags), - "StringSetAsync", - redisKey); + + var task = redisDb.StringSetAsync(redisKey, redisValue, timeToLive, flags: _setValueFlags); if (!task.IsCompleted) await task.ConfigureAwait(false); @@ -176,8 +170,10 @@ public async Task GetMany( { CheckDisposed(); + var redisDb = GetDatabase(); + var valuesFoundCount = 0; - + var tasks = CreateTasks(out var pooledTasksArray); try @@ -185,7 +181,7 @@ public async Task GetMany( await Task .WhenAll(tasks) .ConfigureAwait(false); - + return valuesFoundCount == 0 ? 0 : CopyResultsToDestinationArray(); @@ -194,11 +190,11 @@ await Task { ArrayPool>.Shared.Return(pooledTasksArray); } - + IReadOnlyCollection> CreateTasks(out Task<(TKey, RedisValueWithExpiry)>[] pooledArray) { pooledArray = ArrayPool>.Shared.Rent(keys.Length); - + var i = 0; foreach (var innerKey in keys.Span) pooledArray[i++] = GetSingle(innerKey); @@ -208,11 +204,8 @@ await Task async Task<(TKey, RedisValueWithExpiry)> GetSingle(TKey key) { - var redisKey = _keySerializer(key); - var fromRedis = await _distributedCacheTelemetry.CallAsync( - async () => await GetDatabase() - .StringGetWithExpiryAsync(redisKey) - .ConfigureAwait(false), "StringGetWithExpiryAsync", redisKey) + var fromRedis = await redisDb + .StringGetWithExpiryAsync(_keySerializer(key)) .ConfigureAwait(false); if (!fromRedis.Value.IsNull) @@ -233,7 +226,7 @@ int CopyResultsToDestinationArray() continue; var value = _redisValueConverter.ConvertFromRedisValue(fromRedis.Value); - + span[index++] = new KeyValuePair>( key, new ValueAndTimeToLive(value, fromRedis.Expiry ?? TimeSpan.FromDays(1))); @@ -250,13 +243,15 @@ public async Task SetMany(ReadOnlyMemory> values, Tim { CheckDisposed(); + var redisDb = GetDatabase(); + var pooledTasksArray = ArrayPool.Shared.Rent(values.Length); var toDispose = _serializeValuesToStreams ? ArrayPool.Shared.Rent(values.Length) : null; var tasks = CreateTasks(); - + try { var waitForAllTasksTask = Task.WhenAll(tasks); @@ -267,16 +262,16 @@ public async Task SetMany(ReadOnlyMemory> values, Tim finally { ArrayPool.Shared.Return(pooledTasksArray); - + if (!(toDispose is null)) { for (var i = 0; i < values.Length; i++) toDispose[i]?.Dispose(); - + ArrayPool.Shared.Return(toDispose); } } - + IReadOnlyCollection CreateTasks() { var index = 0; @@ -291,11 +286,9 @@ IReadOnlyCollection CreateTasks() _recentlySetKeysManager?.Mark(((string)redisKey).Substring(_keyPrefixLength)); - pooledTasksArray[index++] = _distributedCacheTelemetry.CallAsync( - () => GetDatabase().StringSetAsync(redisKey, redisValue, timeToLive, flags: _setValueFlags), - "StringSetAsync", redisKey); + pooledTasksArray[index++] = redisDb.StringSetAsync(redisKey, redisValue, timeToLive, flags: _setValueFlags); } - + return new ArraySegment(pooledTasksArray, 0, index); } } @@ -304,24 +297,25 @@ public async Task TryRemove(TKey key) { CheckDisposed(); + var redisDb = GetDatabase(); + var redisKey = _keySerializer(key); _recentlyRemovedKeysManager?.Mark(((string)redisKey).Substring(_keyPrefixLength)); - return await _distributedCacheTelemetry.CallAsync( - () => GetDatabase().KeyDeleteAsync(redisKey), - "KeyDeleteAsync", redisKey) + return await redisDb + .KeyDeleteAsync(redisKey) .ConfigureAwait(false); } public IObservable<(string Key, KeyEventType EventType)> KeysChangedRemotely => _keysChangedRemotely?.AsObservable() ?? Observable.Empty<(string, KeyEventType)>(); - + public void Dispose() { if (_disposed) return; - + _keysChangedRemotely?.Dispose(); _recentlySetKeysManager?.Dispose(); _recentlyRemovedKeysManager?.Dispose(); @@ -334,7 +328,7 @@ private void CheckDisposed() if (_disposed) throw new ObjectDisposedException(this.GetType().ToString()); } - + private void OnKeyChanged(string redisKey, KeyEventType eventType) { bool wasLocalChange; @@ -347,14 +341,14 @@ private void OnKeyChanged(string redisKey, KeyEventType eventType) if (wasLocalChange) return; - + _keysChangedRemotely.OnNext((redisKey, eventType)); } - + [MethodImpl(MethodImplOptions.AggressiveInlining)] private IDatabase GetDatabase() => _connection.Get().GetDatabase(_dbIndex); } - + public sealed class RedisCache : IDistributedCache, IDisposable { private readonly IRedisConnection _connection; @@ -370,12 +364,6 @@ public sealed class RedisCache : IDistributedCache private readonly RecentlySetOrRemovedKeysManager _recentlyRemovedKeysManager; private readonly int _keyPrefixLength; private bool _disposed; - private IDistributedCacheTelemetry _distributedCacheTelemetry = new NoTelemetry(); - - public void SetTelemetry(IDistributedCacheTelemetry distributedCacheTelemetry) - { - _distributedCacheTelemetry = distributedCacheTelemetry; - } public RedisCache( IRedisConnection connection, @@ -408,7 +396,7 @@ public RedisCache( _serializeValuesToStreams = true; } - + public RedisCache( IRedisConnection connection, Func outerKeySerializer, @@ -440,7 +428,7 @@ public RedisCache( _serializeValuesToStreams = false; } - + private RedisCache( IRedisConnection connection, Func outerKeySerializer, @@ -461,23 +449,23 @@ private RedisCache( _setValueFlags = useFireAndForgetWherePossible ? CommandFlags.FireAndForget : CommandFlags.None; _keySeparator = keySeparator; _keyPrefixLength = keyPrefix.ToString().Length; - + if (keyEventTypesToSubscribeTo != KeyEventType.None) { if (subscriber is null) throw new ArgumentNullException(nameof(subscriber)); - + _keysChangedRemotely = new Subject<(string, string, KeyEventType)>(); - + if (keyEventTypesToSubscribeTo.HasFlag(KeyEventType.Set)) _recentlySetKeysManager = new RecentlySetOrRemovedKeysManager(); - + _recentlyRemovedKeysManager = new RecentlySetOrRemovedKeysManager(); - + subscriber.SubscribeToKeyChanges(dbIndex, keyEventTypesToSubscribeTo, OnKeyChanged, keyPrefix); } } - + public async Task GetMany( TOuterKey outerKey, ReadOnlyMemory innerKeys, @@ -485,10 +473,12 @@ public async Task GetMany( { CheckDisposed(); + var redisDb = GetDatabase(); + var redisKeyPrefix = _outerKeySerializer(outerKey).Append(_keySeparator); var valuesFoundCount = 0; - + var tasks = CreateTasks(out var pooledTasksArray); try @@ -496,7 +486,7 @@ public async Task GetMany( await Task .WhenAll(tasks) .ConfigureAwait(false); - + return valuesFoundCount == 0 ? 0 : CopyResultsToDestinationArray(); @@ -509,7 +499,7 @@ await Task IReadOnlyCollection> CreateTasks(out Task<(TInnerKey, RedisValueWithExpiry)>[] pooledArray) { pooledArray = ArrayPool>.Shared.Rent(innerKeys.Length); - + var i = 0; foreach (var innerKey in innerKeys.Span) pooledArray[i++] = GetSingle(innerKey); @@ -521,10 +511,8 @@ await Task { var redisKey = redisKeyPrefix.Append(_innerKeySerializer(innerKey)); - var fromRedis = await _distributedCacheTelemetry.CallAsync( - async () => await GetDatabase() - .StringGetWithExpiryAsync(redisKey) - .ConfigureAwait(false), "StringGetWithExpiryAsync", redisKey) + var fromRedis = await redisDb + .StringGetWithExpiryAsync(redisKey) .ConfigureAwait(false); if (!fromRedis.Value.IsNull) @@ -532,7 +520,7 @@ await Task return (innerKey, fromRedis); } - + int CopyResultsToDestinationArray() { var span = destination.Span; @@ -545,7 +533,7 @@ int CopyResultsToDestinationArray() continue; var value = _redisValueConverter.ConvertFromRedisValue(fromRedis.Value); - + span[index++] = new KeyValuePair>( key, new ValueAndTimeToLive(value, fromRedis.Expiry ?? TimeSpan.FromDays(1))); @@ -565,13 +553,15 @@ public async Task SetMany( { CheckDisposed(); + var redisDb = GetDatabase(); + var redisKeyPrefix = _outerKeySerializer(outerKey).Append(_keySeparator); - + var pooledTasksArray = ArrayPool.Shared.Rent(values.Length); var toDispose = _serializeValuesToStreams ? ArrayPool.Shared.Rent(values.Length) : null; - + var streamIndex = 0; try { @@ -585,12 +575,12 @@ public async Task SetMany( finally { ArrayPool.Shared.Return(pooledTasksArray); - + if (!(toDispose is null)) { for (var i = 0; i < streamIndex; i++) toDispose[i]?.Dispose(); - + ArrayPool.Shared.Return(toDispose); } } @@ -603,38 +593,35 @@ IReadOnlyCollection CreateTasks() var redisKey = redisKeyPrefix.Append(_innerKeySerializer(kv.Key)); if (redisKey == new RedisKey()) // Skip if the key is null continue; - + var redisValue = _redisValueConverter.ConvertToRedisValue(kv.Value, out var stream); if (!(toDispose is null)) toDispose[streamIndex++] = stream; _recentlySetKeysManager?.Mark(((string)redisKey).Substring(_keyPrefixLength)); - - pooledTasksArray[index++] = _distributedCacheTelemetry.CallAsync( - () => GetDatabase().StringSetAsync(redisKey, redisValue, timeToLive, flags: _setValueFlags), - "StringSetAsync", redisKey); + + pooledTasksArray[index++] = redisDb.StringSetAsync(redisKey, redisValue, timeToLive, flags: _setValueFlags); } - + return new ArraySegment(pooledTasksArray, 0, index); } } - + public async Task TryRemove(TOuterKey outerKey, TInnerKey innerKey) { CheckDisposed(); - + + var redisDb = GetDatabase(); + var redisKey = _outerKeySerializer(outerKey) .Append(_keySeparator) .Append(_innerKeySerializer(innerKey)); _recentlyRemovedKeysManager?.Mark(((string)redisKey).Substring(_keyPrefixLength)); - return await _distributedCacheTelemetry.CallAsync( - async () => await GetDatabase() - .KeyDeleteAsync(redisKey) - .ConfigureAwait(false), - "KeyDeleteAsync", redisKey) + return await redisDb + .KeyDeleteAsync(redisKey) .ConfigureAwait(false); } @@ -645,7 +632,7 @@ public void Dispose() { if (_disposed) return; - + _keysChangedRemotely?.Dispose(); _recentlySetKeysManager?.Dispose(); _recentlyRemovedKeysManager?.Dispose(); @@ -658,7 +645,7 @@ private void CheckDisposed() if (_disposed) throw new ObjectDisposedException(this.GetType().ToString()); } - + private void OnKeyChanged(string redisKey, KeyEventType eventType) { bool wasLocalChange; diff --git a/src/CacheMeIfYouCan.Redis/RedisCacheTelemetry.cs b/src/CacheMeIfYouCan.Redis/RedisCacheTelemetry.cs deleted file mode 100644 index 8ea1802d..00000000 --- a/src/CacheMeIfYouCan.Redis/RedisCacheTelemetry.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using System.Threading.Tasks; -using Microsoft.ApplicationInsights.DataContracts; -using Microsoft.ApplicationInsights.Extensibility; - -namespace CacheMeIfYouCan.Redis -{ - internal class RedisCacheTelemetry : IDistributedCacheTelemetry - { - private readonly ITelemetryProcessor _telemetryProcessor; - private readonly string _host; - private readonly string _cacheName; - - public RedisCacheTelemetry(ITelemetryProcessor telemetryProcessor, ITelemetryConfig telemetryConfig, string host, string cacheName) - { - _telemetryProcessor = new RedisTelemetryProcessor(telemetryProcessor, telemetryConfig); - _host = host; - _cacheName = cacheName; - } - - public async Task CallAsync(Func> func, string command, string key) - { - var success = false; - var commandInfoText = $"Command {command}{Environment.NewLine}Key '{key}'"; - - T result; - var startTime = DateTime.UtcNow; - var timer = System.Diagnostics.Stopwatch.StartNew(); - try - { - // making dependency call - result = await func().ConfigureAwait(false); - success = true; - } - finally - { - timer.Stop(); - _telemetryProcessor.Process( - new DependencyTelemetry("Redis", _host, _cacheName, commandInfoText, startTime, timer.Elapsed, "", - success)); - } - return result; - } - } - - internal class NoTelemetry : IDistributedCacheTelemetry - { - public async Task CallAsync(Func> func, string command, string key) - { - return await func().ConfigureAwait(false); - } - } -} \ No newline at end of file diff --git a/src/CacheMeIfYouCan.Redis/TelemetryProcessor.cs b/src/CacheMeIfYouCan.Redis/TelemetryProcessor.cs new file mode 100644 index 00000000..82b20d2f --- /dev/null +++ b/src/CacheMeIfYouCan.Redis/TelemetryProcessor.cs @@ -0,0 +1,34 @@ +using System; +using Microsoft.ApplicationInsights.DataContracts; +using Microsoft.ApplicationInsights.Extensibility; + +namespace CacheMeIfYouCan.Redis +{ + internal class TelemetryProcessor + { + private readonly ITelemetryProcessor _telemetryProcessor; + private readonly IDistributedCacheConfig _config; + + public TelemetryProcessor(IDistributedCacheConfig config, + ITelemetryProcessor telemetryProcessor, + ITelemetryConfig telemetryConfig) + { + _telemetryProcessor = new RedisTelemetryProcessor(telemetryProcessor, telemetryConfig); + _config = config; + } + + public void Add(TimeSpan duration, string command, + string keyOrKeys, bool successful) + { + _telemetryProcessor.Process( + new DependencyTelemetry( + _config.CacheType, + _config.Host, + _config.CacheName, + $"Command {command}{Environment.NewLine}{keyOrKeys}", + DateTime.UtcNow - duration, + duration, + "", successful)); + } + } +} \ No newline at end of file diff --git a/src/CacheMeIfYouCan/DistributedCacheEventsWrapperBase.cs b/src/CacheMeIfYouCan/DistributedCacheEventsWrapperBase.cs index a2f191ef..c0ece521 100644 --- a/src/CacheMeIfYouCan/DistributedCacheEventsWrapperBase.cs +++ b/src/CacheMeIfYouCan/DistributedCacheEventsWrapperBase.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Threading.Tasks; using CacheMeIfYouCan.Internal; @@ -210,11 +209,6 @@ public async Task TryRemove(TKey key) } } - public void SetTelemetry(IDistributedCacheTelemetry distributedCacheTelemetry) - { - _innerCache.SetTelemetry(distributedCacheTelemetry); - } - protected virtual void OnTryRemoveCompletedSuccessfully( TKey key, bool wasRemoved, @@ -360,11 +354,6 @@ public async Task TryRemove(TOuterKey outerKey, TInnerKey innerKey) } } - public void SetTelemetry(IDistributedCacheTelemetry distributedCacheTelemetry) - { - _innerCache.SetTelemetry(distributedCacheTelemetry); - } - protected virtual void OnTryRemoveCompletedSuccessfully( TOuterKey outerKey, TInnerKey innerKey, diff --git a/src/CacheMeIfYouCan/IDistributedCache.cs b/src/CacheMeIfYouCan/IDistributedCache.cs index 71944d78..9c69db5f 100644 --- a/src/CacheMeIfYouCan/IDistributedCache.cs +++ b/src/CacheMeIfYouCan/IDistributedCache.cs @@ -16,8 +16,6 @@ public interface IDistributedCache Task SetMany(ReadOnlyMemory> values, TimeSpan timeToLive); Task TryRemove(TKey key); - - void SetTelemetry(IDistributedCacheTelemetry distributedCacheTelemetry); } public interface IDistributedCache @@ -27,8 +25,6 @@ public interface IDistributedCache Task SetMany(TOuterKey outerKey, ReadOnlyMemory> values, TimeSpan timeToLive); Task TryRemove(TOuterKey outerKey, TInnerKey innerKey); - - void SetTelemetry(IDistributedCacheTelemetry distributedCacheTelemetry); } public static class IDistributedCacheExtensions diff --git a/src/CacheMeIfYouCan/IDistributedCacheConfig.cs b/src/CacheMeIfYouCan/IDistributedCacheConfig.cs new file mode 100644 index 00000000..4acd04fe --- /dev/null +++ b/src/CacheMeIfYouCan/IDistributedCacheConfig.cs @@ -0,0 +1,9 @@ +namespace CacheMeIfYouCan +{ + public interface IDistributedCacheConfig + { + public string CacheType { get; } + public string Host { get; } + public string CacheName { get; } + } +} \ No newline at end of file diff --git a/src/CacheMeIfYouCan/IDistributedCacheTelemetry.cs b/src/CacheMeIfYouCan/IDistributedCacheTelemetry.cs deleted file mode 100644 index 1e04a327..00000000 --- a/src/CacheMeIfYouCan/IDistributedCacheTelemetry.cs +++ /dev/null @@ -1,10 +0,0 @@ -using System; -using System.Threading.Tasks; - -namespace CacheMeIfYouCan -{ - public interface IDistributedCacheTelemetry - { - Task CallAsync(Func> func, string command, string key); - } -} \ No newline at end of file diff --git a/src/CacheMeIfYouCan/ITelemetryConfig.cs b/src/CacheMeIfYouCan/ITelemetryConfig.cs index 7cee6e46..cdd87f71 100644 --- a/src/CacheMeIfYouCan/ITelemetryConfig.cs +++ b/src/CacheMeIfYouCan/ITelemetryConfig.cs @@ -1,7 +1,10 @@ namespace CacheMeIfYouCan { + /// + /// Settings used to control what telemetry is collected + /// public interface ITelemetryConfig { - public int MillisecondThreshold { get; } + int MillisecondThreshold { get; } } -} \ No newline at end of file +} diff --git a/tests/CacheMeIfYouCan.Redis.Tests/DistributedCacheConfig.cs b/tests/CacheMeIfYouCan.Redis.Tests/DistributedCacheConfig.cs new file mode 100644 index 00000000..356bd198 --- /dev/null +++ b/tests/CacheMeIfYouCan.Redis.Tests/DistributedCacheConfig.cs @@ -0,0 +1,9 @@ +namespace CacheMeIfYouCan.Redis.Tests +{ + public class DistributedCacheConfig : IDistributedCacheConfig + { + public string CacheType { get; set; } + public string Host { get; set; } + public string CacheName { get; set; } + } +} \ No newline at end of file diff --git a/tests/CacheMeIfYouCan.Redis.Tests/MockTelemetryConfig.cs b/tests/CacheMeIfYouCan.Redis.Tests/MockTelemetryConfig.cs deleted file mode 100644 index c1447064..00000000 --- a/tests/CacheMeIfYouCan.Redis.Tests/MockTelemetryConfig.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace CacheMeIfYouCan.Redis.Tests -{ - public class MockTelemetryConfig : ITelemetryConfig - { - public MockTelemetryConfig(int millisecondThreshold) - { - MillisecondThreshold = millisecondThreshold; - } - - public int MillisecondThreshold { get; } - } -} \ No newline at end of file diff --git a/tests/CacheMeIfYouCan.Redis.Tests/RedisCacheTests1.cs b/tests/CacheMeIfYouCan.Redis.Tests/RedisCacheTests1.cs index 47a69083..7fa4c7bf 100644 --- a/tests/CacheMeIfYouCan.Redis.Tests/RedisCacheTests1.cs +++ b/tests/CacheMeIfYouCan.Redis.Tests/RedisCacheTests1.cs @@ -343,12 +343,17 @@ await cache4 public async Task TestTelemetryOnCommand(int threshold, bool anyTrace) { using var connection = BuildConnection(); - using var cache = BuildRedisCache(connection, useSerializer: false); - var mockTelemetry = new MockTelemetryProcessor(); - var config = new MockTelemetryConfig(threshold); - - cache.WithApplicationInsightsTelemetry(mockTelemetry, config, "host", "TestCache1"); + var cacheConfig = new DistributedCacheConfig + { + CacheName = "Test1", + CacheType = "Redis", + Host = "Here" + }; + var mockTelemetryConfig = new TelemetryConfig {MillisecondThreshold = threshold}; + var mockTelemetry = new TelemetryProcessor(); + var cache = BuildDistributedCache(connection, useSerializer: false) + .WithApplicationInsightsTelemetry(cacheConfig, mockTelemetry, mockTelemetryConfig); const int elementsToCache = 10; @@ -371,13 +376,20 @@ public async Task TestTelemetryOnCommand(int threshold, bool anyTrace) if (anyTrace) { trace.Should().NotBeEmpty(); - trace.Count.Should().BeLessOrEqualTo(elementsToCache); - trace.First().Command.Should().Contain("StringSetAsync"); - trace.Last().Command.Should().Contain("StringSetAsync"); + trace.Count.Should().Be(1); + var commandText = trace.First().Command; + commandText.Should().Contain("StringSetAsync"); + commandText.Should().Contain("Keys"); + commandText.Split("Keys ").Length.Should().Be(2); + var actualKeys = commandText.Split("Keys ")[1].Replace("'", ""); + actualKeys.Should().Contain(","); + var actualKeyList = actualKeys.Split(","); + actualKeyList.Length.Should().Be(elementsToCache); + actualKeyList.First().Should().Be("1"); + actualKeyList.Last().Should().Be("10"); } } - private static RedisConnection BuildConnection() => new RedisConnection(TestConnectionString.Value); private static RedisCache BuildRedisCache( @@ -412,5 +424,38 @@ private static RedisCache BuildRedisCache( subscriber: connection, keyEventTypesToSubscribeTo: keyEventTypesToSubscribeTo); } + + private static IDistributedCache BuildDistributedCache( + RedisConnection connection, + bool useFireAndForget = false, + string keyPrefix = null, + string nullValue = null, + bool useSerializer = false, + KeyEventType keyEventTypesToSubscribeTo = KeyEventType.None) + { + if (useSerializer) + { + return new RedisCache( + connection, + k => k.ToString(), + new ProtoBufSerializer(), + useFireAndForgetWherePossible: useFireAndForget, + keyPrefix: keyPrefix ?? Guid.NewGuid().ToString(), + nullValue: nullValue, + subscriber: connection, + keyEventTypesToSubscribeTo: keyEventTypesToSubscribeTo); + } + + return new RedisCache( + connection, + k => k.ToString(), + v => v.ToString(), + v => TestClass.Parse(v), + useFireAndForgetWherePossible: useFireAndForget, + keyPrefix: keyPrefix ?? Guid.NewGuid().ToString(), + nullValue: nullValue, + subscriber: connection, + keyEventTypesToSubscribeTo: keyEventTypesToSubscribeTo); + } } } \ No newline at end of file diff --git a/tests/CacheMeIfYouCan.Redis.Tests/RedisCacheTests2.cs b/tests/CacheMeIfYouCan.Redis.Tests/RedisCacheTests2.cs index ae05801f..1788f65e 100644 --- a/tests/CacheMeIfYouCan.Redis.Tests/RedisCacheTests2.cs +++ b/tests/CacheMeIfYouCan.Redis.Tests/RedisCacheTests2.cs @@ -256,12 +256,17 @@ await cache4 public async Task TestTelemetryOnCommand(int threshold, bool anyTrace) { using var connection = BuildConnection(); - using var cache = BuildRedisCache(connection); - - var mockTelemetry = new MockTelemetryProcessor(); - var mockConfig = new MockTelemetryConfig(threshold); - cache.WithApplicationInsightsTelemetry(mockTelemetry, mockConfig, "host", "TestCache2"); + var cacheConfig = new DistributedCacheConfig + { + CacheName = "Test2", + CacheType = "Redis", + Host = "Here" + }; + var mockTelemetryConfig = new TelemetryConfig { MillisecondThreshold = threshold }; + var mockTelemetry = new TelemetryProcessor(); + var cache = BuildDistributedCache(connection, useSerializer: false) + .WithApplicationInsightsTelemetry(cacheConfig, mockTelemetry, mockTelemetryConfig); const int elementsToCache = 10; @@ -282,9 +287,18 @@ public async Task TestTelemetryOnCommand(int threshold, bool anyTrace) if (anyTrace) { trace.Should().NotBeEmpty(); - trace.Count.Should().BeLessOrEqualTo(elementsToCache); - trace.First().Command.Should().Contain("StringSetAsync"); - trace.Last().Command.Should().Contain("StringSetAsync"); + trace.Count.Should().Be(1); + var commandText = trace.First().Command; + commandText.Should().Contain("StringSetAsync"); + commandText.Should().Contain("Keys"); + commandText.Split("Keys ").Length.Should().Be(2); + var actualKeys = commandText.Split("Keys ")[1].Replace("'", ""); + actualKeys.Should().Contain(","); + var actualKeyList = actualKeys.Split(","); + actualKeyList.Length.Should().Be(elementsToCache / 2); + var firstKey = actualKeyList.First(); + firstKey.Should().NotBeNullOrEmpty(); + firstKey.Should().Be("1.2"); } } @@ -326,5 +340,42 @@ private static RedisCache BuildRedisCache( subscriber: connection, keyEventTypesToSubscribeTo: keyEventTypesToSubscribeTo); } + + private static IDistributedCache BuildDistributedCache( + RedisConnection connection, + bool useFireAndForget = false, + string keyPrefix = null, + string nullValue = null, + bool useSerializer = false, + KeyEventType keyEventTypesToSubscribeTo = KeyEventType.None) + { + if (useSerializer) + { + return new RedisCache( + connection, + k => k.ToString(), + k => k.ToString(), + new ProtoBufSerializer(), + useFireAndForgetWherePossible: useFireAndForget, + keySeparator: "_", + keyPrefix: keyPrefix ?? Guid.NewGuid().ToString(), + nullValue: nullValue, + subscriber: connection, + keyEventTypesToSubscribeTo: keyEventTypesToSubscribeTo); + } + + return new RedisCache( + connection, + k => k.ToString(), + k => k.ToString(), + v => v.ToString(), + v => TestClass.Parse(v), + useFireAndForgetWherePossible: useFireAndForget, + keySeparator: "_", + keyPrefix: keyPrefix ?? Guid.NewGuid().ToString(), + nullValue: nullValue, + subscriber: connection, + keyEventTypesToSubscribeTo: keyEventTypesToSubscribeTo); + } } } \ No newline at end of file diff --git a/tests/CacheMeIfYouCan.Redis.Tests/TelemetryConfig.cs b/tests/CacheMeIfYouCan.Redis.Tests/TelemetryConfig.cs new file mode 100644 index 00000000..5d9b1518 --- /dev/null +++ b/tests/CacheMeIfYouCan.Redis.Tests/TelemetryConfig.cs @@ -0,0 +1,7 @@ +namespace CacheMeIfYouCan.Redis.Tests +{ + public class TelemetryConfig : ITelemetryConfig + { + public int MillisecondThreshold { get; set; } + } +} diff --git a/tests/CacheMeIfYouCan.Redis.Tests/MockTelemetryProcessor.cs b/tests/CacheMeIfYouCan.Redis.Tests/TelemetryProcessor.cs similarity index 93% rename from tests/CacheMeIfYouCan.Redis.Tests/MockTelemetryProcessor.cs rename to tests/CacheMeIfYouCan.Redis.Tests/TelemetryProcessor.cs index 5b35f54f..2f4afdb3 100644 --- a/tests/CacheMeIfYouCan.Redis.Tests/MockTelemetryProcessor.cs +++ b/tests/CacheMeIfYouCan.Redis.Tests/TelemetryProcessor.cs @@ -6,7 +6,7 @@ namespace CacheMeIfYouCan.Redis.Tests { - public class MockTelemetryProcessor : ITelemetryProcessor + public class TelemetryProcessor : ITelemetryProcessor { private readonly List _telemetry = new List(); diff --git a/tests/CacheMeIfYouCan.Tests/MockDistributedCache.cs b/tests/CacheMeIfYouCan.Tests/MockDistributedCache.cs index dd0d075f..1fc7f6ff 100644 --- a/tests/CacheMeIfYouCan.Tests/MockDistributedCache.cs +++ b/tests/CacheMeIfYouCan.Tests/MockDistributedCache.cs @@ -98,10 +98,6 @@ public Task TryRemove(TKey key) return Task.FromResult(_innerCache.TryRemove(key, out _)); } - public void SetTelemetry(IDistributedCacheTelemetry distributedCacheTelemetry) - { - } - public void ThrowExceptionOnNextAction() => _throwExceptionOnNextAction = true; private void ThrowIfRequested() @@ -184,10 +180,6 @@ public Task TryRemove(TOuterKey outerKey, TInnerKey innerKey) return Task.FromResult(_innerCache.TryRemove(outerKey, innerKey, out _)); } - public void SetTelemetry(IDistributedCacheTelemetry distributedCacheTelemetry) - { - } - public void ThrowExceptionOnNextAction() => _throwExceptionOnNextAction = true; private void ThrowIfRequested()