diff --git a/Foundatio.All.sln b/Foundatio.All.sln index ec3e0e7a7..a8a126e05 100644 --- a/Foundatio.All.sln +++ b/Foundatio.All.sln @@ -22,8 +22,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{A1DF EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Foundatio", "src\Foundatio\Foundatio.csproj", "{392A3FAB-0067-4A9E-968A-0919B565B51C}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Foundatio.MetricsNET", "src\Foundatio.MetricsNET\Foundatio.MetricsNET.csproj", "{F9AEEE11-9E18-4FFE-A962-EB7281BCD561}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Foundatio.HostingSample", "samples\Foundatio.HostingSample\Foundatio.HostingSample.csproj", "{39AFF0C0-D64A-4D57-871F-17D9BF2E5A93}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Foundatio.TestHarness", "src\Foundatio.TestHarness\Foundatio.TestHarness.csproj", "{6A0F1A4B-C0D2-423C-9B9D-7E75B91B41EE}" @@ -36,8 +34,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Foundatio.JsonNet", "src\Fo EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Foundatio.Utf8Json", "src\Foundatio.Utf8Json\Foundatio.Utf8Json.csproj", "{44A5A11C-4D9E-4219-9E56-46F07E37A3B0}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Foundatio.AppMetrics", "src\Foundatio.AppMetrics\Foundatio.AppMetrics.csproj", "{D97811C5-186A-4ED3-8675-925A7ACD32F6}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Foundatio.Extensions.Hosting", "src\Foundatio.Extensions.Hosting\Foundatio.Extensions.Hosting.csproj", "{FC503293-E2AD-4FE8-856E-F2605D0422D1}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Foundatio.MessagePack", "src\Foundatio.MessagePack\Foundatio.MessagePack.csproj", "{1D0CF7B3-D0B3-4739-A280-5C0822169E08}" @@ -116,10 +112,6 @@ Global {392A3FAB-0067-4A9E-968A-0919B565B51C}.Debug|Any CPU.Build.0 = Debug|Any CPU {392A3FAB-0067-4A9E-968A-0919B565B51C}.Release|Any CPU.ActiveCfg = Release|Any CPU {392A3FAB-0067-4A9E-968A-0919B565B51C}.Release|Any CPU.Build.0 = Release|Any CPU - {F9AEEE11-9E18-4FFE-A962-EB7281BCD561}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F9AEEE11-9E18-4FFE-A962-EB7281BCD561}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F9AEEE11-9E18-4FFE-A962-EB7281BCD561}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F9AEEE11-9E18-4FFE-A962-EB7281BCD561}.Release|Any CPU.Build.0 = Release|Any CPU {39AFF0C0-D64A-4D57-871F-17D9BF2E5A93}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {39AFF0C0-D64A-4D57-871F-17D9BF2E5A93}.Debug|Any CPU.Build.0 = Debug|Any CPU {39AFF0C0-D64A-4D57-871F-17D9BF2E5A93}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -144,10 +136,6 @@ Global {44A5A11C-4D9E-4219-9E56-46F07E37A3B0}.Debug|Any CPU.Build.0 = Debug|Any CPU {44A5A11C-4D9E-4219-9E56-46F07E37A3B0}.Release|Any CPU.ActiveCfg = Release|Any CPU {44A5A11C-4D9E-4219-9E56-46F07E37A3B0}.Release|Any CPU.Build.0 = Release|Any CPU - {D97811C5-186A-4ED3-8675-925A7ACD32F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D97811C5-186A-4ED3-8675-925A7ACD32F6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D97811C5-186A-4ED3-8675-925A7ACD32F6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D97811C5-186A-4ED3-8675-925A7ACD32F6}.Release|Any CPU.Build.0 = Release|Any CPU {FC503293-E2AD-4FE8-856E-F2605D0422D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {FC503293-E2AD-4FE8-856E-F2605D0422D1}.Debug|Any CPU.Build.0 = Debug|Any CPU {FC503293-E2AD-4FE8-856E-F2605D0422D1}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/Foundatio.sln b/Foundatio.sln index f4bb98303..d771108ad 100644 --- a/Foundatio.sln +++ b/Foundatio.sln @@ -21,8 +21,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Samples", "Samples", "{A1DF EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Foundatio", "src\Foundatio\Foundatio.csproj", "{392A3FAB-0067-4A9E-968A-0919B565B51C}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Foundatio.MetricsNET", "src\Foundatio.MetricsNET\Foundatio.MetricsNET.csproj", "{F9AEEE11-9E18-4FFE-A962-EB7281BCD561}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Foundatio.HostingSample", "samples\Foundatio.HostingSample\Foundatio.HostingSample.csproj", "{39AFF0C0-D64A-4D57-871F-17D9BF2E5A93}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Foundatio.TestHarness", "src\Foundatio.TestHarness\Foundatio.TestHarness.csproj", "{6A0F1A4B-C0D2-423C-9B9D-7E75B91B41EE}" @@ -35,8 +33,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Foundatio.JsonNet", "src\Fo EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Foundatio.Utf8Json", "src\Foundatio.Utf8Json\Foundatio.Utf8Json.csproj", "{44A5A11C-4D9E-4219-9E56-46F07E37A3B0}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Foundatio.AppMetrics", "src\Foundatio.AppMetrics\Foundatio.AppMetrics.csproj", "{D97811C5-186A-4ED3-8675-925A7ACD32F6}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Foundatio.Extensions.Hosting", "src\Foundatio.Extensions.Hosting\Foundatio.Extensions.Hosting.csproj", "{FC503293-E2AD-4FE8-856E-F2605D0422D1}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Foundatio.MessagePack", "src\Foundatio.MessagePack\Foundatio.MessagePack.csproj", "{1D0CF7B3-D0B3-4739-A280-5C0822169E08}" @@ -65,18 +61,6 @@ Global {392A3FAB-0067-4A9E-968A-0919B565B51C}.Release|x64.Build.0 = Release|Any CPU {392A3FAB-0067-4A9E-968A-0919B565B51C}.Release|x86.ActiveCfg = Release|Any CPU {392A3FAB-0067-4A9E-968A-0919B565B51C}.Release|x86.Build.0 = Release|Any CPU - {F9AEEE11-9E18-4FFE-A962-EB7281BCD561}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F9AEEE11-9E18-4FFE-A962-EB7281BCD561}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F9AEEE11-9E18-4FFE-A962-EB7281BCD561}.Debug|x64.ActiveCfg = Debug|Any CPU - {F9AEEE11-9E18-4FFE-A962-EB7281BCD561}.Debug|x64.Build.0 = Debug|Any CPU - {F9AEEE11-9E18-4FFE-A962-EB7281BCD561}.Debug|x86.ActiveCfg = Debug|Any CPU - {F9AEEE11-9E18-4FFE-A962-EB7281BCD561}.Debug|x86.Build.0 = Debug|Any CPU - {F9AEEE11-9E18-4FFE-A962-EB7281BCD561}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F9AEEE11-9E18-4FFE-A962-EB7281BCD561}.Release|Any CPU.Build.0 = Release|Any CPU - {F9AEEE11-9E18-4FFE-A962-EB7281BCD561}.Release|x64.ActiveCfg = Release|Any CPU - {F9AEEE11-9E18-4FFE-A962-EB7281BCD561}.Release|x64.Build.0 = Release|Any CPU - {F9AEEE11-9E18-4FFE-A962-EB7281BCD561}.Release|x86.ActiveCfg = Release|Any CPU - {F9AEEE11-9E18-4FFE-A962-EB7281BCD561}.Release|x86.Build.0 = Release|Any CPU {39AFF0C0-D64A-4D57-871F-17D9BF2E5A93}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {39AFF0C0-D64A-4D57-871F-17D9BF2E5A93}.Debug|Any CPU.Build.0 = Debug|Any CPU {39AFF0C0-D64A-4D57-871F-17D9BF2E5A93}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -149,18 +133,6 @@ Global {44A5A11C-4D9E-4219-9E56-46F07E37A3B0}.Release|x64.Build.0 = Release|Any CPU {44A5A11C-4D9E-4219-9E56-46F07E37A3B0}.Release|x86.ActiveCfg = Release|Any CPU {44A5A11C-4D9E-4219-9E56-46F07E37A3B0}.Release|x86.Build.0 = Release|Any CPU - {D97811C5-186A-4ED3-8675-925A7ACD32F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D97811C5-186A-4ED3-8675-925A7ACD32F6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D97811C5-186A-4ED3-8675-925A7ACD32F6}.Debug|x64.ActiveCfg = Debug|Any CPU - {D97811C5-186A-4ED3-8675-925A7ACD32F6}.Debug|x64.Build.0 = Debug|Any CPU - {D97811C5-186A-4ED3-8675-925A7ACD32F6}.Debug|x86.ActiveCfg = Debug|Any CPU - {D97811C5-186A-4ED3-8675-925A7ACD32F6}.Debug|x86.Build.0 = Debug|Any CPU - {D97811C5-186A-4ED3-8675-925A7ACD32F6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D97811C5-186A-4ED3-8675-925A7ACD32F6}.Release|Any CPU.Build.0 = Release|Any CPU - {D97811C5-186A-4ED3-8675-925A7ACD32F6}.Release|x64.ActiveCfg = Release|Any CPU - {D97811C5-186A-4ED3-8675-925A7ACD32F6}.Release|x64.Build.0 = Release|Any CPU - {D97811C5-186A-4ED3-8675-925A7ACD32F6}.Release|x86.ActiveCfg = Release|Any CPU - {D97811C5-186A-4ED3-8675-925A7ACD32F6}.Release|x86.Build.0 = Release|Any CPU {FC503293-E2AD-4FE8-856E-F2605D0422D1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {FC503293-E2AD-4FE8-856E-F2605D0422D1}.Debug|Any CPU.Build.0 = Debug|Any CPU {FC503293-E2AD-4FE8-856E-F2605D0422D1}.Debug|x64.ActiveCfg = Debug|Any CPU diff --git a/samples/Foundatio.HostingSample/Jobs/Sample2Job.cs b/samples/Foundatio.HostingSample/Jobs/Sample2Job.cs index df6e44aa2..6dc2900e8 100644 --- a/samples/Foundatio.HostingSample/Jobs/Sample2Job.cs +++ b/samples/Foundatio.HostingSample/Jobs/Sample2Job.cs @@ -2,7 +2,6 @@ using System.Threading; using System.Threading.Tasks; using Foundatio.Jobs; -using Foundatio.Utility; using Microsoft.Extensions.Diagnostics.HealthChecks; using Microsoft.Extensions.Logging; @@ -22,7 +21,7 @@ public Sample2Job(ILoggerFactory loggerFactory) public Task RunAsync(CancellationToken cancellationToken = default) { - _lastRun = SystemClock.UtcNow; + _lastRun = DateTime.UtcNow; Interlocked.Increment(ref _iterationCount); if (_logger.IsEnabled(LogLevel.Information)) _logger.LogTrace("Sample2Job Run #{IterationCount} Thread={ManagedThreadId}", _iterationCount, Thread.CurrentThread.ManagedThreadId); @@ -35,7 +34,7 @@ public Task CheckHealthAsync(HealthCheckContext context, Canc if (!_lastRun.HasValue) return Task.FromResult(HealthCheckResult.Healthy("Job has not been run yet.")); - if (SystemClock.UtcNow.Subtract(_lastRun.Value) > TimeSpan.FromSeconds(5)) + if (DateTime.UtcNow.Subtract(_lastRun.Value) > TimeSpan.FromSeconds(5)) return Task.FromResult(HealthCheckResult.Unhealthy("Job has not run in the last 5 seconds.")); return Task.FromResult(HealthCheckResult.Healthy("Job has run in the last 5 seconds.")); diff --git a/samples/Foundatio.HostingSample/Program.cs b/samples/Foundatio.HostingSample/Program.cs index 258d0b7d2..aadc37f00 100644 --- a/samples/Foundatio.HostingSample/Program.cs +++ b/samples/Foundatio.HostingSample/Program.cs @@ -6,8 +6,6 @@ using Foundatio.Caching; using Foundatio.Extensions.Hosting.Jobs; using Foundatio.Extensions.Hosting.Startup; -using Foundatio.Jobs; -using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; diff --git a/src/Foundatio.AppMetrics/AppMetricsClient.cs b/src/Foundatio.AppMetrics/AppMetricsClient.cs deleted file mode 100644 index 638104e0f..000000000 --- a/src/Foundatio.AppMetrics/AppMetricsClient.cs +++ /dev/null @@ -1,35 +0,0 @@ -using App.Metrics; -using App.Metrics.Counter; -using App.Metrics.Gauge; -using App.Metrics.Timer; - -namespace Foundatio.Metrics; - -#pragma warning disable CS0618 // Type or member is obsolete -public class AppMetricsClient : IMetricsClient -#pragma warning restore CS0618 // Type or member is obsolete -{ - private readonly IMetrics _metrics; - - public AppMetricsClient(IMetrics metrics) - { - _metrics = metrics; - } - - public void Counter(string name, int value = 1) - { - _metrics.Provider.Counter.Instance(new CounterOptions { Name = name }).Increment(value); - } - - public void Gauge(string name, double value) - { - _metrics.Provider.Gauge.Instance(new GaugeOptions { Name = name }).SetValue(value); - } - - public void Timer(string name, int milliseconds) - { - _metrics.Provider.Timer.Instance(new TimerOptions { Name = name }).Record(milliseconds, TimeUnit.Milliseconds); - } - - public void Dispose() { } -} diff --git a/src/Foundatio.AppMetrics/Foundatio.AppMetrics.csproj b/src/Foundatio.AppMetrics/Foundatio.AppMetrics.csproj deleted file mode 100644 index f7568453e..000000000 --- a/src/Foundatio.AppMetrics/Foundatio.AppMetrics.csproj +++ /dev/null @@ -1,11 +0,0 @@ - - - $(PackageTags);AppMetrics;Logging;Log - - - - - - - - diff --git a/src/Foundatio.DataProtection/Foundatio.DataProtection.csproj b/src/Foundatio.DataProtection/Foundatio.DataProtection.csproj index e13a6b374..56cde43f1 100644 --- a/src/Foundatio.DataProtection/Foundatio.DataProtection.csproj +++ b/src/Foundatio.DataProtection/Foundatio.DataProtection.csproj @@ -3,7 +3,7 @@ $(PackageTags);DataProtection - + diff --git a/src/Foundatio.Extensions.Hosting/Jobs/JobHostExtensions.cs b/src/Foundatio.Extensions.Hosting/Jobs/JobHostExtensions.cs index ca0be9adc..1beff32c7 100644 --- a/src/Foundatio.Extensions.Hosting/Jobs/JobHostExtensions.cs +++ b/src/Foundatio.Extensions.Hosting/Jobs/JobHostExtensions.cs @@ -2,7 +2,6 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using Foundatio.Extensions.Hosting.Startup; using Foundatio.Jobs; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; diff --git a/src/Foundatio.Extensions.Hosting/Jobs/ScheduledJobRunner.cs b/src/Foundatio.Extensions.Hosting/Jobs/ScheduledJobRunner.cs index 7f11e7516..27193ddd2 100644 --- a/src/Foundatio.Extensions.Hosting/Jobs/ScheduledJobRunner.cs +++ b/src/Foundatio.Extensions.Hosting/Jobs/ScheduledJobRunner.cs @@ -18,7 +18,7 @@ internal class ScheduledJobRunner private readonly ScheduledJobOptions _jobOptions; private readonly IServiceProvider _serviceProvider; private readonly ICacheClient _cacheClient; - private readonly ISystemClock _systemClock; + private readonly TimeProvider _timeProvider; private CronExpression _cronSchedule; private readonly ILockProvider _lockProvider; private readonly ILogger _logger; @@ -30,7 +30,7 @@ public ScheduledJobRunner(ScheduledJobOptions jobOptions, IServiceProvider servi _jobOptions = jobOptions; _jobOptions.Name ??= Guid.NewGuid().ToString("N").Substring(0, 10); _serviceProvider = serviceProvider; - _systemClock = serviceProvider.GetService() ?? SystemClock.Instance; + _timeProvider = serviceProvider.GetService() ?? TimeProvider.System; _cacheClient = new ScopedCacheClient(cacheClient, "jobs"); _logger = loggerFactory?.CreateLogger() ?? NullLogger.Instance; @@ -40,7 +40,7 @@ public ScheduledJobRunner(ScheduledJobOptions jobOptions, IServiceProvider servi var interval = TimeSpan.FromDays(1); - var nextOccurrence = _cronSchedule.GetNextOccurrence(_systemClock.UtcNow()); + var nextOccurrence = _cronSchedule.GetNextOccurrence(_timeProvider.GetUtcNow().UtcDateTime); if (nextOccurrence.HasValue) { var nextNextOccurrence = _cronSchedule.GetNextOccurrence(nextOccurrence.Value); @@ -50,7 +50,7 @@ public ScheduledJobRunner(ScheduledJobOptions jobOptions, IServiceProvider servi _lockProvider = new ThrottlingLockProvider(_cacheClient, 1, interval.Add(interval)); - NextRun = _cronSchedule.GetNextOccurrence(_systemClock.UtcNow()); + NextRun = _cronSchedule.GetNextOccurrence(_timeProvider.GetUtcNow().UtcDateTime); } public ScheduledJobOptions Options => _jobOptions; @@ -62,7 +62,7 @@ public string Schedule set { _cronSchedule = CronExpression.Parse(value); - NextRun = _cronSchedule.GetNextOccurrence(_systemClock.UtcNow()); + NextRun = _cronSchedule.GetNextOccurrence(_timeProvider.GetUtcNow().UtcDateTime); _schedule = value; } } @@ -87,7 +87,7 @@ public async ValueTask ShouldRunAsync() return false; // not time yet - if (NextRun > _systemClock.UtcNow()) + if (NextRun > _timeProvider.GetUtcNow().UtcDateTime) return false; // check if already run diff --git a/src/Foundatio.Extensions.Hosting/Jobs/ScheduledJobService.cs b/src/Foundatio.Extensions.Hosting/Jobs/ScheduledJobService.cs index ce2970197..db521a865 100644 --- a/src/Foundatio.Extensions.Hosting/Jobs/ScheduledJobService.cs +++ b/src/Foundatio.Extensions.Hosting/Jobs/ScheduledJobService.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using System.Threading; using System.Threading.Tasks; using Foundatio.Extensions.Hosting.Startup; diff --git a/src/Foundatio.Extensions.Hosting/Startup/StartupActionsContext.cs b/src/Foundatio.Extensions.Hosting/Startup/StartupActionsContext.cs index a23ec2003..0004edacf 100644 --- a/src/Foundatio.Extensions.Hosting/Startup/StartupActionsContext.cs +++ b/src/Foundatio.Extensions.Hosting/Startup/StartupActionsContext.cs @@ -28,27 +28,27 @@ internal void MarkStartupComplete(RunStartupActionsResult result) public async Task WaitForStartupAsync(CancellationToken cancellationToken, TimeSpan? maxTimeToWait = null) { bool isFirstWaiter = Interlocked.Increment(ref _waitCount) == 1; - var startTime = SystemClock.UtcNow; - var lastStatus = SystemClock.UtcNow; + var startTime = DateTime.UtcNow; + var lastStatus = DateTime.UtcNow; maxTimeToWait ??= TimeSpan.FromMinutes(5); - while (!cancellationToken.IsCancellationRequested && SystemClock.UtcNow.Subtract(startTime) < maxTimeToWait) + while (!cancellationToken.IsCancellationRequested && DateTime.UtcNow.Subtract(startTime) < maxTimeToWait) { if (IsStartupComplete) return Result; - if (isFirstWaiter && SystemClock.UtcNow.Subtract(lastStatus) > TimeSpan.FromSeconds(5) && _logger.IsEnabled(LogLevel.Information)) + if (isFirstWaiter && DateTime.UtcNow.Subtract(lastStatus) > TimeSpan.FromSeconds(5) && _logger.IsEnabled(LogLevel.Information)) { - lastStatus = SystemClock.UtcNow; - _logger.LogInformation("Waiting for startup actions to be completed for {Duration:mm\\:ss}...", SystemClock.UtcNow.Subtract(startTime)); + lastStatus = DateTime.UtcNow; + _logger.LogInformation("Waiting for startup actions to be completed for {Duration:mm\\:ss}...", DateTime.UtcNow.Subtract(startTime)); } await Task.Delay(1000, cancellationToken).AnyContext(); } if (isFirstWaiter && _logger.IsEnabled(LogLevel.Error)) - _logger.LogError("Timed out waiting for startup actions to be completed after {Duration:mm\\:ss}", SystemClock.UtcNow.Subtract(startTime)); + _logger.LogError("Timed out waiting for startup actions to be completed after {Duration:mm\\:ss}", DateTime.UtcNow.Subtract(startTime)); - return new RunStartupActionsResult { Success = false, ErrorMessage = $"Timed out waiting for startup actions to be completed after {SystemClock.UtcNow.Subtract(startTime):mm\\:ss}" }; + return new RunStartupActionsResult { Success = false, ErrorMessage = $"Timed out waiting for startup actions to be completed after {DateTime.UtcNow.Subtract(startTime):mm\\:ss}" }; } } diff --git a/src/Foundatio.MetricsNET/Foundatio.MetricsNET.csproj b/src/Foundatio.MetricsNET/Foundatio.MetricsNET.csproj deleted file mode 100644 index 930076504..000000000 --- a/src/Foundatio.MetricsNET/Foundatio.MetricsNET.csproj +++ /dev/null @@ -1,11 +0,0 @@ - - - False - - - - - - - - \ No newline at end of file diff --git a/src/Foundatio.MetricsNET/MetricsNETClient.cs b/src/Foundatio.MetricsNET/MetricsNETClient.cs deleted file mode 100644 index beb952a4d..000000000 --- a/src/Foundatio.MetricsNET/MetricsNETClient.cs +++ /dev/null @@ -1,25 +0,0 @@ -using Metrics; - -namespace Foundatio.Metrics; - -#pragma warning disable CS0618 // Type or member is obsolete -public class MetricsNETClient : IMetricsClient -#pragma warning restore CS0618 // Type or member is obsolete -{ - public void Counter(string name, int value = 1) - { - Metric.Counter(name, Unit.None).Increment(); - } - - public void Gauge(string name, double value) - { - Metric.Gauge(name, () => value, Unit.None); - } - - public void Timer(string name, int milliseconds) - { - Metric.Timer(name, Unit.Calls, SamplingType.SlidingWindow, TimeUnit.Milliseconds).Record(milliseconds, TimeUnit.Milliseconds); - } - - public void Dispose() { } -} diff --git a/src/Foundatio.TestHarness/Caching/CacheClientTestsBase.cs b/src/Foundatio.TestHarness/Caching/CacheClientTestsBase.cs index 75ee4ad12..e95d892d5 100644 --- a/src/Foundatio.TestHarness/Caching/CacheClientTestsBase.cs +++ b/src/Foundatio.TestHarness/Caching/CacheClientTestsBase.cs @@ -1,13 +1,15 @@ using System; using System.Collections; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; using Foundatio.Caching; -using Foundatio.Metrics; using Foundatio.Utility; using Foundatio.Xunit; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Time.Testing; using Xunit; using Xunit.Abstractions; @@ -442,7 +444,7 @@ public virtual async Task CanSetExpirationAsync() { await cache.RemoveAllAsync(); - var expiresAt = SystemClock.UtcNow.AddMilliseconds(300); + var expiresAt = DateTime.UtcNow.AddMilliseconds(300); bool success = await cache.SetAsync("test", 1, expiresAt); Assert.True(success); success = await cache.SetAsync("test2", 1, expiresAt.AddMilliseconds(100)); @@ -450,7 +452,7 @@ public virtual async Task CanSetExpirationAsync() Assert.Equal(1, (await cache.GetAsync("test")).Value); Assert.True((await cache.GetExpirationAsync("test")).Value < TimeSpan.FromSeconds(1)); - await SystemClock.SleepAsync(500); + await Task.Delay(500); Assert.False((await cache.GetAsync("test")).HasValue); Assert.Null(await cache.GetExpirationAsync("test")); Assert.False((await cache.GetAsync("test2")).HasValue); @@ -468,26 +470,24 @@ public virtual async Task CanSetMinMaxExpirationAsync() { await cache.RemoveAllAsync(); - using (TestSystemClock.Install()) - { - var now = DateTime.UtcNow; - TestSystemClock.SetFrozenTime(now); - - var expires = DateTime.MaxValue - now.AddDays(1); - Assert.True(await cache.SetAsync("test1", 1, expires)); - Assert.False(await cache.SetAsync("test2", 1, DateTime.MinValue)); - Assert.True(await cache.SetAsync("test3", 1, DateTime.MaxValue)); - Assert.True(await cache.SetAsync("test4", 1, DateTime.MaxValue - now.AddDays(-1))); - - Assert.Equal(1, (await cache.GetAsync("test1")).Value); - Assert.InRange((await cache.GetExpirationAsync("test1")).Value, expires.Subtract(TimeSpan.FromSeconds(10)), expires); - - Assert.False(await cache.ExistsAsync("test2")); - Assert.Equal(1, (await cache.GetAsync("test3")).Value); - Assert.False((await cache.GetExpirationAsync("test3")).HasValue); - Assert.Equal(1, (await cache.GetAsync("test4")).Value); - Assert.False((await cache.GetExpirationAsync("test4")).HasValue); - } + var timeProvider = new FakeTimeProvider(); + var now = DateTime.UtcNow; + timeProvider.SetUtcNow(now); + + var expires = DateTime.MaxValue - now.AddDays(1); + Assert.True(await cache.SetAsync("test1", 1, expires)); + Assert.False(await cache.SetAsync("test2", 1, DateTime.MinValue)); + Assert.True(await cache.SetAsync("test3", 1, DateTime.MaxValue)); + Assert.True(await cache.SetAsync("test4", 1, DateTime.MaxValue - now.AddDays(-1))); + + Assert.Equal(1, (await cache.GetAsync("test1")).Value); + Assert.InRange((await cache.GetExpirationAsync("test1")).Value, expires.Subtract(TimeSpan.FromSeconds(10)), expires); + + Assert.False(await cache.ExistsAsync("test2")); + Assert.Equal(1, (await cache.GetAsync("test3")).Value); + Assert.False((await cache.GetExpirationAsync("test3")).HasValue); + Assert.Equal(1, (await cache.GetAsync("test4")).Value); + Assert.False((await cache.GetExpirationAsync("test4")).HasValue); } } @@ -533,7 +533,7 @@ public virtual async Task CanIncrementAndExpireAsync() Assert.Equal(1, newVal); - await SystemClock.SleepAsync(1500); + await Task.Delay(1500); Assert.False((await cache.GetAsync("test")).HasValue); } } @@ -626,29 +626,28 @@ public virtual async Task CanGetAndSetDateTimeAsync() { await cache.RemoveAllAsync(); - DateTime value = SystemClock.UtcNow.Floor(TimeSpan.FromSeconds(1)); + DateTime value = DateTime.UtcNow.Floor(TimeSpan.FromSeconds(1)); long unixTimeValue = value.ToUnixTimeSeconds(); Assert.True(await cache.SetUnixTimeSecondsAsync("test", value)); Assert.Equal(unixTimeValue, await cache.GetAsync("test", 0)); var actual = await cache.GetUnixTimeSecondsAsync("test"); Assert.Equal(value.Ticks, actual.Ticks); - Assert.Equal(value.Kind, actual.Kind); + Assert.Equal(TimeSpan.Zero, actual.Offset); - value = SystemClock.Now.Floor(TimeSpan.FromMilliseconds(1)); + value = DateTime.Now.Floor(TimeSpan.FromMilliseconds(1)); unixTimeValue = value.ToUnixTimeMilliseconds(); Assert.True(await cache.SetUnixTimeMillisecondsAsync("test", value)); Assert.Equal(unixTimeValue, await cache.GetAsync("test", 0)); actual = (await cache.GetUnixTimeMillisecondsAsync("test")).ToLocalTime(); Assert.Equal(value.Ticks, actual.Ticks); - Assert.Equal(value.Kind, actual.Kind); - value = SystemClock.UtcNow.Floor(TimeSpan.FromMilliseconds(1)); + value = DateTime.UtcNow.Floor(TimeSpan.FromMilliseconds(1)); unixTimeValue = value.ToUnixTimeMilliseconds(); Assert.True(await cache.SetUnixTimeMillisecondsAsync("test", value)); Assert.Equal(unixTimeValue, await cache.GetAsync("test", 0)); actual = await cache.GetUnixTimeMillisecondsAsync("test"); Assert.Equal(value.Ticks, actual.Ticks); - Assert.Equal(value.Kind, actual.Kind); + Assert.Equal(TimeSpan.Zero, actual.Offset); var lowerValue = value - TimeSpan.FromHours(1); var lowerUnixTimeValue = lowerValue.ToUnixTimeMilliseconds(); @@ -822,9 +821,8 @@ public virtual async Task MeasureThroughputAsync() { await cache.RemoveAllAsync(); - var start = SystemClock.UtcNow; + var sw = Stopwatch.StartNew(); const int itemCount = 10000; - var metrics = new InMemoryMetricsClient(new InMemoryMetricsClientOptions()); for (int i = 0; i < itemCount; i++) { await cache.SetAsync("test", 13422); @@ -832,10 +830,9 @@ public virtual async Task MeasureThroughputAsync() Assert.Equal(13422, (await cache.GetAsync("test")).Value); Assert.Null(await cache.GetAsync("test2")); Assert.True((await cache.GetAsync("flag")).Value); - metrics.Counter("work"); } - - var workCounter = metrics.GetCounterStatsAsync("work", start, SystemClock.UtcNow); + sw.Stop(); + _logger.LogInformation("Time: {0}ms", sw.ElapsedMilliseconds); } } @@ -849,9 +846,8 @@ public virtual async Task MeasureSerializerSimpleThroughputAsync() { await cache.RemoveAllAsync(); - var start = SystemClock.UtcNow; + var sw = Stopwatch.StartNew(); const int itemCount = 10000; - var metrics = new InMemoryMetricsClient(new InMemoryMetricsClientOptions()); for (int i = 0; i < itemCount; i++) { await cache.SetAsync("test", new SimpleModel @@ -863,10 +859,9 @@ public virtual async Task MeasureSerializerSimpleThroughputAsync() Assert.True(model.HasValue); Assert.Equal("Hello", model.Value.Data1); Assert.Equal(12, model.Value.Data2); - metrics.Counter("work"); } - - var workCounter = metrics.GetCounterStatsAsync("work", start, SystemClock.UtcNow); + sw.Stop(); + _logger.LogInformation("Time: {0}ms", sw.ElapsedMilliseconds); } } @@ -880,9 +875,8 @@ public virtual async Task MeasureSerializerComplexThroughputAsync() { await cache.RemoveAllAsync(); - var start = SystemClock.UtcNow; + var sw = Stopwatch.StartNew(); const int itemCount = 10000; - var metrics = new InMemoryMetricsClient(new InMemoryMetricsClientOptions()); for (int i = 0; i < itemCount; i++) { await cache.SetAsync("test", new ComplexModel @@ -918,10 +912,9 @@ public virtual async Task MeasureSerializerComplexThroughputAsync() Assert.True(model.HasValue); Assert.Equal("Hello", model.Value.Data1); Assert.Equal(12, model.Value.Data2); - metrics.Counter("work"); } - - var workCounter = metrics.GetCounterStatsAsync("work", start, SystemClock.UtcNow); + sw.Stop(); + _logger.LogInformation("Time: {0}ms", sw.ElapsedMilliseconds); } } } diff --git a/src/Foundatio.TestHarness/Extensions/TaskExtensions.cs b/src/Foundatio.TestHarness/Extensions/TaskExtensions.cs index eeaef9334..c8172ca8c 100644 --- a/src/Foundatio.TestHarness/Extensions/TaskExtensions.cs +++ b/src/Foundatio.TestHarness/Extensions/TaskExtensions.cs @@ -24,6 +24,6 @@ public static async Task WaitAsync(this AsyncAutoResetEvent resetEvent, TimeSpan public static Task WaitAsync(this AsyncCountdownEvent countdownEvent, TimeSpan timeout) { - return Task.WhenAny(countdownEvent.WaitAsync(), SystemClock.SleepAsync(timeout)); + return Task.WhenAny(countdownEvent.WaitAsync(), Task.Delay(timeout)); } } diff --git a/src/Foundatio.TestHarness/Foundatio.TestHarness.csproj b/src/Foundatio.TestHarness/Foundatio.TestHarness.csproj index 9c62ba7ff..f9b6c9872 100644 --- a/src/Foundatio.TestHarness/Foundatio.TestHarness.csproj +++ b/src/Foundatio.TestHarness/Foundatio.TestHarness.csproj @@ -7,6 +7,7 @@ + diff --git a/src/Foundatio.TestHarness/Jobs/HelloWorldJob.cs b/src/Foundatio.TestHarness/Jobs/HelloWorldJob.cs index 429ad68e7..cfa75da48 100644 --- a/src/Foundatio.TestHarness/Jobs/HelloWorldJob.cs +++ b/src/Foundatio.TestHarness/Jobs/HelloWorldJob.cs @@ -10,7 +10,7 @@ public class HelloWorldJob : JobBase { private readonly string _id; - public HelloWorldJob(ILoggerFactory loggerFactory) : base(loggerFactory) + public HelloWorldJob(TimeProvider timeProvider, ILoggerFactory loggerFactory) : base(timeProvider, loggerFactory) { _id = Guid.NewGuid().ToString("N").Substring(0, 10); } @@ -36,7 +36,7 @@ public class FailingJob : JobBase public int RunCount { get; set; } - public FailingJob(ILoggerFactory loggerFactory) : base(loggerFactory) + public FailingJob(TimeProvider timeProvider, ILoggerFactory loggerFactory) : base(timeProvider, loggerFactory) { _id = Guid.NewGuid().ToString("N").Substring(0, 10); } @@ -57,7 +57,7 @@ public class LongRunningJob : JobBase private readonly string _id; private int _iterationCount; - public LongRunningJob(ILoggerFactory loggerFactory) : base(loggerFactory) + public LongRunningJob(TimeProvider timeProvider, ILoggerFactory loggerFactory) : base(timeProvider, loggerFactory) { _id = Guid.NewGuid().ToString("N").Substring(0, 10); } diff --git a/src/Foundatio.TestHarness/Jobs/JobQueueTestsBase.cs b/src/Foundatio.TestHarness/Jobs/JobQueueTestsBase.cs index 979b37b33..dcdfd55bc 100644 --- a/src/Foundatio.TestHarness/Jobs/JobQueueTestsBase.cs +++ b/src/Foundatio.TestHarness/Jobs/JobQueueTestsBase.cs @@ -8,9 +8,8 @@ using Foundatio.Caching; using Foundatio.Jobs; using Foundatio.Lock; -using Foundatio.Metrics; using Foundatio.Queues; -using Foundatio.Utility; +using Foundatio.Tests.Metrics; using Foundatio.Xunit; using Microsoft.Extensions.Logging; using Xunit; @@ -53,7 +52,7 @@ public virtual async Task ActivityWillFlowThroughQueueJobAsync() var enqueueTask = await queue.EnqueueAsync(new SampleQueueWorkItem { - Created = SystemClock.UtcNow, + Created = DateTime.UtcNow, Path = "somepath" }); @@ -76,12 +75,12 @@ public virtual async Task CanRunQueueJobAsync() var enqueueTask = Parallel.ForEachAsync(Enumerable.Range(1, workItemCount), async (index, _) => await queue.EnqueueAsync(new SampleQueueWorkItem { - Created = SystemClock.UtcNow, + Created = DateTime.UtcNow, Path = "somepath" + index })); var job = new SampleQueueJob(queue, null, Log); - await SystemClock.SleepAsync(10); + await Task.Delay(10); await Task.WhenAll(job.RunUntilEmptyAsync(), enqueueTask); var stats = await queue.GetQueueStatsAsync(); @@ -104,14 +103,14 @@ public virtual async Task CanRunQueueJobWithLockFailAsync() _logger.LogInformation($"Enqueue #{index}"); await queue.EnqueueAsync(new SampleQueueWorkItem { - Created = SystemClock.UtcNow, + Created = DateTime.UtcNow, Path = "somepath" + index }); }); - var lockProvider = new ThrottlingLockProvider(new InMemoryCacheClient(o => o.LoggerFactory(Log)), allowedLockCount, TimeSpan.FromDays(1), Log); - var job = new SampleQueueJobWithLocking(queue, null, lockProvider, Log); - await SystemClock.SleepAsync(10); + var lockProvider = new ThrottlingLockProvider(new InMemoryCacheClient(o => o.LoggerFactory(Log)), allowedLockCount, TimeSpan.FromDays(1), null, Log); + var job = new SampleQueueJobWithLocking(queue, lockProvider, null, Log); + await Task.Delay(10); _logger.LogInformation("Starting RunUntilEmptyAsync"); await Task.WhenAll(job.RunUntilEmptyAsync(), enqueueTask); _logger.LogInformation("Done RunUntilEmptyAsync"); @@ -130,19 +129,16 @@ public virtual async Task CanRunMultipleQueueJobsAsync() const int workItemCount = 100; Log.SetLogLevel(LogLevel.Information); - Log.SetLogLevel(LogLevel.None); - using var metrics = new InMemoryMetricsClient(new InMemoryMetricsClientOptions { LoggerFactory = Log, Buffered = true }); var queues = new List>(); try { + using var metricsCollector = new DiagnosticsMetricsCollector(FoundatioDiagnostics.Meter.Name, _logger); + for (int i = 0; i < jobCount; i++) { var q = GetSampleWorkItemQueue(retries: 1, retryDelay: TimeSpan.Zero); await q.DeleteQueueAsync(); -#pragma warning disable CS0618 // Type or member is obsolete - q.AttachBehavior(new MetricsQueueBehavior(metrics, "test", loggerFactory: Log)); -#pragma warning restore CS0618 // Type or member is obsolete queues.Add(q); } _logger.LogInformation("Done setting up queues"); @@ -152,7 +148,7 @@ public virtual async Task CanRunMultipleQueueJobsAsync() var queue = queues[RandomData.GetInt(0, jobCount - 1)]; await queue.EnqueueAsync(new SampleQueueWorkItem { - Created = SystemClock.UtcNow, + Created = DateTime.UtcNow, Path = RandomData.GetString() }); }); @@ -162,7 +158,7 @@ await queue.EnqueueAsync(new SampleQueueWorkItem await Parallel.ForEachAsync(Enumerable.Range(1, jobCount), async (index, _) => { var queue = queues[index - 1]; - var job = new SampleQueueWithRandomErrorsAndAbandonsJob(queue, metrics, Log); + var job = new SampleQueueWithRandomErrorsAndAbandonsJob(queue, null, Log); await job.RunUntilEmptyAsync(cancellationTokenSource.Token); await cancellationTokenSource.CancelAsync(); }); @@ -180,11 +176,6 @@ await Parallel.ForEachAsync(Enumerable.Range(1, jobCount), async (index, _) => } _logger.LogInformation("Done getting queue stats"); - await metrics.FlushAsync(); - _logger.LogInformation("Done flushing metrics"); - - var queueSummary = await metrics.GetQueueStatsAsync("test.samplequeueworkitem"); - Assert.Equal(queueStats.Sum(s => s.Completed), queueSummary.Completed.Count); Assert.InRange(queueStats.Sum(s => s.Completed), 0, workItemCount); } finally diff --git a/src/Foundatio.TestHarness/Jobs/SampleQueueJob.cs b/src/Foundatio.TestHarness/Jobs/SampleQueueJob.cs index 35ea7e271..c8110adae 100644 --- a/src/Foundatio.TestHarness/Jobs/SampleQueueJob.cs +++ b/src/Foundatio.TestHarness/Jobs/SampleQueueJob.cs @@ -6,7 +6,6 @@ using Exceptionless; using Foundatio.Jobs; using Foundatio.Lock; -using Foundatio.Metrics; using Foundatio.Queues; using Microsoft.Extensions.Logging; @@ -14,59 +13,44 @@ namespace Foundatio.Tests.Jobs; public class SampleQueueWithRandomErrorsAndAbandonsJob : QueueJobBase { - private readonly IMetricsClient _metrics; - - public SampleQueueWithRandomErrorsAndAbandonsJob(IQueue queue, IMetricsClient metrics, ILoggerFactory loggerFactory = null) : base(queue, loggerFactory) + public SampleQueueWithRandomErrorsAndAbandonsJob(IQueue queue, TimeProvider timeProvider, ILoggerFactory loggerFactory = null) : base(queue, timeProvider, loggerFactory) { - _metrics = metrics ?? NullMetricsClient.Instance; } protected override Task ProcessQueueEntryAsync(QueueEntryContext context) { - _metrics.Counter("dequeued"); - if (RandomData.GetBool(10)) { - _metrics.Counter("errors"); throw new Exception("Boom!"); } if (RandomData.GetBool(10)) { - _metrics.Counter("abandoned"); return Task.FromResult(JobResult.FailedWithMessage("Abandoned")); } - _metrics.Counter("completed"); return Task.FromResult(JobResult.Success); } } public class SampleQueueJob : QueueJobBase { - private readonly IMetricsClient _metrics; - - public SampleQueueJob(IQueue queue, IMetricsClient metrics, ILoggerFactory loggerFactory = null) : base(queue, loggerFactory) + public SampleQueueJob(IQueue queue, TimeProvider timeProvider, ILoggerFactory loggerFactory = null) : base(queue, timeProvider, loggerFactory) { - _metrics = metrics ?? NullMetricsClient.Instance; } protected override Task ProcessQueueEntryAsync(QueueEntryContext context) { - _metrics.Counter("dequeued"); - _metrics.Counter("completed"); return Task.FromResult(JobResult.Success); } } public class SampleQueueJobWithLocking : QueueJobBase { - private readonly IMetricsClient _metrics; private readonly ILockProvider _lockProvider; - public SampleQueueJobWithLocking(IQueue queue, IMetricsClient metrics, ILockProvider lockProvider, ILoggerFactory loggerFactory = null) : base(queue, loggerFactory) + public SampleQueueJobWithLocking(IQueue queue, ILockProvider lockProvider, TimeProvider timeProvider, ILoggerFactory loggerFactory = null) : base(queue, timeProvider, loggerFactory) { - _metrics = metrics ?? NullMetricsClient.Instance; _lockProvider = lockProvider; } @@ -80,7 +64,6 @@ public SampleQueueJobWithLocking(IQueue queue, IMetricsClie protected override Task ProcessQueueEntryAsync(QueueEntryContext context) { - _metrics.Counter("completed"); return Task.FromResult(JobResult.Success); } } @@ -93,30 +76,22 @@ public class SampleQueueWorkItem public class SampleJob : JobBase { - private readonly IMetricsClient _metrics; - - public SampleJob(IMetricsClient metrics, ILoggerFactory loggerFactory) : base(loggerFactory) + public SampleJob(TimeProvider timeProvider, ILoggerFactory loggerFactory) : base(timeProvider, loggerFactory) { - _metrics = metrics; } protected override Task RunInternalAsync(JobContext context) { - _metrics.Counter("runs"); - if (RandomData.GetBool(10)) { - _metrics.Counter("errors"); throw new Exception("Boom!"); } if (RandomData.GetBool(10)) { - _metrics.Counter("failed"); return Task.FromResult(JobResult.FailedWithMessage("Failed")); } - _metrics.Counter("completed"); return Task.FromResult(JobResult.Success); } } diff --git a/src/Foundatio.TestHarness/Jobs/ThrottledJob.cs b/src/Foundatio.TestHarness/Jobs/ThrottledJob.cs index ade785ef6..38e69ca2d 100644 --- a/src/Foundatio.TestHarness/Jobs/ThrottledJob.cs +++ b/src/Foundatio.TestHarness/Jobs/ThrottledJob.cs @@ -12,7 +12,7 @@ public class ThrottledJob : JobWithLockBase { public ThrottledJob(ICacheClient client, ILoggerFactory loggerFactory = null) : base(loggerFactory) { - _locker = new ThrottlingLockProvider(client, 1, TimeSpan.FromMilliseconds(100), loggerFactory); + _locker = new ThrottlingLockProvider(client, 1, TimeSpan.FromMilliseconds(100), null, loggerFactory); } private readonly ILockProvider _locker; diff --git a/src/Foundatio.TestHarness/Jobs/WithDependencyJob.cs b/src/Foundatio.TestHarness/Jobs/WithDependencyJob.cs index 2dcb855f2..e35adfb3d 100644 --- a/src/Foundatio.TestHarness/Jobs/WithDependencyJob.cs +++ b/src/Foundatio.TestHarness/Jobs/WithDependencyJob.cs @@ -6,7 +6,7 @@ namespace Foundatio.Tests.Jobs; public class WithDependencyJob : JobBase { - public WithDependencyJob(MyDependency dependency, ILoggerFactory loggerFactory = null) : base(loggerFactory) + public WithDependencyJob(MyDependency dependency, ILoggerFactory loggerFactory = null) : base(null, loggerFactory) { Dependency = dependency; } diff --git a/src/Foundatio.TestHarness/Jobs/WithLockingJob.cs b/src/Foundatio.TestHarness/Jobs/WithLockingJob.cs index 4b21bb441..853c95dde 100644 --- a/src/Foundatio.TestHarness/Jobs/WithLockingJob.cs +++ b/src/Foundatio.TestHarness/Jobs/WithLockingJob.cs @@ -5,7 +5,6 @@ using Foundatio.Jobs; using Foundatio.Lock; using Foundatio.Messaging; -using Foundatio.Utility; using Microsoft.Extensions.Logging; using Xunit; @@ -17,7 +16,7 @@ public class WithLockingJob : JobWithLockBase public WithLockingJob(ILoggerFactory loggerFactory) : base(loggerFactory) { - _locker = new CacheLockProvider(new InMemoryCacheClient(o => o.LoggerFactory(loggerFactory)), new InMemoryMessageBus(o => o.LoggerFactory(loggerFactory)), loggerFactory); + _locker = new CacheLockProvider(new InMemoryCacheClient(o => o.LoggerFactory(loggerFactory)), new InMemoryMessageBus(o => o.LoggerFactory(loggerFactory)), null, loggerFactory); } public int RunCount { get; set; } @@ -31,7 +30,7 @@ protected override async Task RunInternalAsync(JobContext context) { RunCount++; - await SystemClock.SleepAsync(150, context.CancellationToken); + await Task.Delay(150, context.CancellationToken); Assert.True(await _locker.IsLockedAsync("WithLockingJob")); return JobResult.Success; diff --git a/src/Foundatio.TestHarness/Locks/LockTestBase.cs b/src/Foundatio.TestHarness/Locks/LockTestBase.cs index b6e66a46f..bfd390766 100644 --- a/src/Foundatio.TestHarness/Locks/LockTestBase.cs +++ b/src/Foundatio.TestHarness/Locks/LockTestBase.cs @@ -43,7 +43,7 @@ public virtual async Task CanAcquireAndReleaseLockAsync() Assert.NotNull(lock1); Assert.True(await locker.IsLockedAsync("test")); var lock2Task = locker.AcquireAsync("test", acquireTimeout: TimeSpan.FromMilliseconds(250)); - await SystemClock.SleepAsync(TimeSpan.FromMilliseconds(250)); + await Task.Delay(TimeSpan.FromMilliseconds(250)); Assert.Null(await lock2Task); } finally @@ -335,7 +335,7 @@ await Task.Run(async () => private Task DoLockedWorkAsync(ILockProvider locker) { - return locker.TryUsingAsync("DoLockedWork", async () => await SystemClock.SleepAsync(500), TimeSpan.FromMinutes(1), TimeSpan.Zero); + return locker.TryUsingAsync("DoLockedWork", async () => await Task.Delay(500), TimeSpan.FromMinutes(1), TimeSpan.Zero); } public virtual async Task WillThrottleCallsAsync() @@ -353,7 +353,7 @@ public virtual async Task WillThrottleCallsAsync() string lockName = Guid.NewGuid().ToString("N").Substring(0, 10); // sleep until start of throttling period - while (SystemClock.UtcNow.Ticks % period.Ticks < TimeSpan.TicksPerMillisecond * 100) + while (DateTime.UtcNow.Ticks % period.Ticks < TimeSpan.TicksPerMillisecond * 100) Thread.Sleep(10); var sw = Stopwatch.StartNew(); diff --git a/src/Foundatio.TestHarness/Messaging/MessageBusTestBase.cs b/src/Foundatio.TestHarness/Messaging/MessageBusTestBase.cs index e60abac88..2a70a038e 100644 --- a/src/Foundatio.TestHarness/Messaging/MessageBusTestBase.cs +++ b/src/Foundatio.TestHarness/Messaging/MessageBusTestBase.cs @@ -74,7 +74,7 @@ await messageBus.SubscribeAsync>(msg => _logger.LogTrace("Set event"); }); - await SystemClock.SleepAsync(1000); + await Task.Delay(1000); await messageBus.PublishAsync(new SimpleMessageA { Data = "Hello", @@ -114,7 +114,7 @@ await messageBus.SubscribeAsync(msg => _logger.LogTrace("Set event"); }); - await SystemClock.SleepAsync(100); + await Task.Delay(100); await messageBus.PublishAsync(new SimpleMessageA { Data = "Hello", @@ -146,7 +146,7 @@ await messageBus.SubscribeAsync(msg => throw new Exception(); }); - await SystemClock.SleepAsync(100); + await Task.Delay(100); await messageBus.PublishAsync(null); _logger.LogTrace("Published one..."); @@ -176,7 +176,7 @@ await messageBus.SubscribeAsync(msg => _logger.LogTrace("Set event"); }); - await SystemClock.SleepAsync(100); + await Task.Delay(100); await messageBus.PublishAsync(new DerivedSimpleMessageA { Data = "Hello" @@ -212,7 +212,7 @@ await messageBus.SubscribeAsync(msg => _logger.LogTrace("Set event"); }); - await SystemClock.SleepAsync(100); + await Task.Delay(100); await messageBus.PublishAsync(new SimpleMessageA { Data = "Hello" @@ -327,7 +327,7 @@ await bus.SubscribeAsync(msg => var subscribe = Parallel.ForEachAsync(Enumerable.Range(1, iterations), async (i, ct) => { - await SystemClock.SleepAsync(RandomData.GetInt(0, 10), ct); + await Task.Delay(RandomData.GetInt(0, 10), ct); await messageBuses.Random().SubscribeAsync(msg => Task.CompletedTask, cancellationToken: ct); }); @@ -605,7 +605,7 @@ await messageBus.PublishAsync(new SimpleMessageA Data = "Hello" }); - await SystemClock.SleepAsync(100); + await Task.Delay(100); await messageBus.SubscribeAsync(msg => { Assert.Equal("Hello", msg.Data); diff --git a/src/Foundatio.TestHarness/Metrics/DiagnosticsMetricsCollector.cs b/src/Foundatio.TestHarness/Metrics/DiagnosticsMetricsCollector.cs index ce4f05aea..e8b1a6281 100644 --- a/src/Foundatio.TestHarness/Metrics/DiagnosticsMetricsCollector.cs +++ b/src/Foundatio.TestHarness/Metrics/DiagnosticsMetricsCollector.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Immutable; @@ -15,6 +15,7 @@ namespace Foundatio.Tests.Metrics; public class DiagnosticsMetricsCollector : IDisposable { + private readonly Timer _timer; private readonly MeterListener _meterListener = new(); private readonly ConcurrentQueue> _byteMeasurements = new(); private readonly ConcurrentQueue> _shortMeasurements = new(); @@ -23,13 +24,13 @@ public class DiagnosticsMetricsCollector : IDisposable private readonly ConcurrentQueue> _floatMeasurements = new(); private readonly ConcurrentQueue> _doubleMeasurements = new(); private readonly ConcurrentQueue> _decimalMeasurements = new(); - private readonly int _maxMeasurementCountPerType = 1000; + private readonly int _maxMeasurementCountPerType; private readonly AsyncAutoResetEvent _measurementEvent = new(false); private readonly ILogger _logger; - public DiagnosticsMetricsCollector(string metricNameOrPrefix, ILogger logger, int maxMeasurementCountPerType = 1000) : this(n => n.StartsWith(metricNameOrPrefix), logger, maxMeasurementCountPerType) { } + public DiagnosticsMetricsCollector(string metricNameOrPrefix, ILogger logger, int maxMeasurementCountPerType = 100000) : this(n => n.StartsWith(metricNameOrPrefix), logger, maxMeasurementCountPerType) { } - public DiagnosticsMetricsCollector(Func shouldCollect, ILogger logger, int maxMeasurementCount = 1000) + public DiagnosticsMetricsCollector(Func shouldCollect, ILogger logger, int maxMeasurementCount = 100000) { _logger = logger; _maxMeasurementCountPerType = maxMeasurementCount; @@ -97,6 +98,8 @@ public DiagnosticsMetricsCollector(Func shouldCollect, ILogger log }); _meterListener.Start(); + + _timer = new Timer(_ => RecordObservableInstruments(), null, TimeSpan.Zero, TimeSpan.FromMilliseconds(50)); } public void RecordObservableInstruments() @@ -314,7 +317,7 @@ public async Task WaitForCounterAsync(string name, Func work, lon cancellationToken = cancellationTokenSource.Token; } - var start = SystemClock.UtcNow; + var start = DateTime.UtcNow; var currentCount = (int)GetSum(name); var targetCount = currentCount + count; @@ -336,7 +339,7 @@ public async Task WaitForCounterAsync(string name, Func work, lon _logger.LogTrace("Got new measurement: count={CurrentCount} expected={Count}", currentCount, targetCount); } - _logger.LogTrace("Done waiting: count={CurrentCount} expected={Count} success={Success} time={Time}", currentCount, targetCount, currentCount >= targetCount, SystemClock.UtcNow.Subtract(start)); + _logger.LogTrace("Done waiting: count={CurrentCount} expected={Count} success={Success} time={Time}", currentCount, targetCount, currentCount >= targetCount, DateTime.UtcNow.Subtract(start)); return currentCount >= targetCount; } @@ -344,6 +347,7 @@ public async Task WaitForCounterAsync(string name, Func work, lon public void Dispose() { GC.SuppressFinalize(this); + _timer.Dispose(); _meterListener?.Dispose(); } } diff --git a/src/Foundatio.TestHarness/Metrics/MetricsClientTestBase.cs b/src/Foundatio.TestHarness/Metrics/MetricsClientTestBase.cs deleted file mode 100644 index 5608847c6..000000000 --- a/src/Foundatio.TestHarness/Metrics/MetricsClientTestBase.cs +++ /dev/null @@ -1,220 +0,0 @@ -using System; -using System.Diagnostics; -using System.Threading; -using System.Threading.Tasks; -using Foundatio.Metrics; -using Foundatio.Queues; -using Foundatio.Tests.Queue; -using Foundatio.Utility; -using Foundatio.Xunit; -using Microsoft.Extensions.Logging; -using Xunit; -using Xunit.Abstractions; -#pragma warning disable AsyncFixer04 // A disposable object used in a fire & forget async call -#pragma warning disable 612, 618 - -namespace Foundatio.Tests.Metrics; - -public abstract class MetricsClientTestBase : TestWithLoggingBase -{ - public MetricsClientTestBase(ITestOutputHelper output) : base(output) { } - - public abstract IMetricsClient GetMetricsClient(bool buffered = false); - - public virtual async Task CanSetGaugesAsync() - { - using var metrics = GetMetricsClient(); - - if (metrics is not IMetricsClientStats stats) - return; - - metrics.Gauge("mygauge", 12d); - Assert.Equal(12d, (await stats.GetGaugeStatsAsync("mygauge")).Last); - metrics.Gauge("mygauge", 10d); - metrics.Gauge("mygauge", 5d); - metrics.Gauge("mygauge", 4d); - metrics.Gauge("mygauge", 12d); - metrics.Gauge("mygauge", 20d); - Assert.Equal(20d, (await stats.GetGaugeStatsAsync("mygauge")).Last); - } - - public virtual async Task CanIncrementCounterAsync() - { - using var metrics = GetMetricsClient(); - - if (metrics is not IMetricsClientStats stats) - return; - - metrics.Counter("c1"); - await AssertCounterAsync(stats, "c1", 1); - - metrics.Counter("c1", 5); - await AssertCounterAsync(stats, "c1", 6); - - metrics.Gauge("g1", 2.534); - Assert.Equal(2.534, await stats.GetLastGaugeValueAsync("g1")); - - metrics.Timer("t1", 50788); - var timer = await stats.GetTimerStatsAsync("t1"); - Assert.Equal(1, timer.Count); - - if (_logger.IsEnabled(LogLevel.Information)) - _logger.LogInformation((await stats.GetCounterStatsAsync("c1")).ToString()); - } - - private Task AssertCounterAsync(IMetricsClientStats client, string name, long expected) - { - return Run.WithRetriesAsync(async () => - { - long actual = await client.GetCounterCountAsync(name, SystemClock.UtcNow.Subtract(TimeSpan.FromHours(1))); - Assert.Equal(expected, actual); - }, 8, logger: _logger); - } - - public virtual async Task CanGetBufferedQueueMetricsAsync() - { - using var metrics = GetMetricsClient(true) as IBufferedMetricsClient; - - if (metrics is not IMetricsClientStats stats) - return; - - using var behavior = new MetricsQueueBehavior(metrics, reportCountsInterval: TimeSpan.FromMilliseconds(25), loggerFactory: Log); - using var queue = new InMemoryQueue(new InMemoryQueueOptions { Behaviors = new[] { behavior }, LoggerFactory = Log }); - - await queue.EnqueueAsync(new SimpleWorkItem { Id = 1, Data = "1" }); - await SystemClock.SleepAsync(50); - var entry = await queue.DequeueAsync(TimeSpan.Zero); - await SystemClock.SleepAsync(15); - await entry.CompleteAsync(); - - await SystemClock.SleepAsync(100); // give queue metrics time - await metrics.FlushAsync(); - var queueStats = await stats.GetQueueStatsAsync("simpleworkitem"); - Assert.Equal(1, queueStats.Count.Max); - Assert.Equal(0, queueStats.Count.Last); - Assert.Equal(1, queueStats.Enqueued.Count); - Assert.Equal(1, queueStats.Dequeued.Count); - Assert.Equal(1, queueStats.Completed.Count); - Assert.InRange(queueStats.QueueTime.AverageDuration, 45, 250); - Assert.InRange(queueStats.ProcessTime.AverageDuration, 10, 250); - } - - public virtual async Task CanIncrementBufferedCounterAsync() - { - using var metrics = GetMetricsClient(true) as IBufferedMetricsClient; - - if (metrics is not IMetricsClientStats stats) - return; - - metrics.Counter("c1"); - await metrics.FlushAsync(); - var counter = await stats.GetCounterStatsAsync("c1", SystemClock.UtcNow.AddMinutes(-5), SystemClock.UtcNow); - Assert.Equal(1, counter.Count); - - metrics.Counter("c1", 5); - await metrics.FlushAsync(); - counter = await stats.GetCounterStatsAsync("c1", SystemClock.UtcNow.AddMinutes(-5), SystemClock.UtcNow); - Assert.Equal(6, counter.Count); - - metrics.Gauge("g1", 5.34); - await metrics.FlushAsync(); - var gauge = await stats.GetGaugeStatsAsync("g1", SystemClock.UtcNow.AddMinutes(-5), SystemClock.UtcNow); - Assert.Equal(5.34, gauge.Last); - Assert.Equal(5.34, gauge.Max); - - metrics.Gauge("g1", 2.534); - await metrics.FlushAsync(); - gauge = await stats.GetGaugeStatsAsync("g1", SystemClock.UtcNow.AddMinutes(-5), SystemClock.UtcNow); - Assert.Equal(2.534, gauge.Last); - Assert.Equal(5.34, gauge.Max); - - metrics.Timer("t1", 50788); - await metrics.FlushAsync(); - var timer = await stats.GetTimerStatsAsync("t1", SystemClock.UtcNow.AddMinutes(-5), SystemClock.UtcNow); - Assert.Equal(1, timer.Count); - Assert.Equal(50788, timer.TotalDuration); - - metrics.Timer("t1", 98); - metrics.Timer("t1", 102); - await metrics.FlushAsync(); - timer = await stats.GetTimerStatsAsync("t1", SystemClock.UtcNow.AddMinutes(-5), SystemClock.UtcNow); - Assert.Equal(3, timer.Count); - Assert.Equal(50788 + 98 + 102, timer.TotalDuration); - } - -#pragma warning disable 4014 - public virtual async Task CanWaitForCounterAsync() - { - const string CounterName = "Test"; - using var metrics = GetMetricsClient() as CacheBucketMetricsClientBase; - - if (metrics is not IMetricsClientStats stats) - return; - - Task.Run(async () => - { - await SystemClock.SleepAsync(TimeSpan.FromMilliseconds(50)); - metrics.Counter(CounterName); - }); - - var sw = Stopwatch.StartNew(); - var task = metrics.WaitForCounterAsync(CounterName, 1, TimeSpan.FromMilliseconds(500)); - await SystemClock.SleepAsync(TimeSpan.FromMilliseconds(100)); - Assert.True(await task, $"Expected at least 1 count within 500 ms... Took: {sw.Elapsed:g}"); - - Task.Run(async () => - { - await SystemClock.SleepAsync(TimeSpan.FromMilliseconds(50)); - metrics.Counter(CounterName); - }); - - sw.Restart(); - task = metrics.WaitForCounterAsync(CounterName, timeout: TimeSpan.FromMilliseconds(500)); - await SystemClock.SleepAsync(TimeSpan.FromMilliseconds(100)); - Assert.True(await task, $"Expected at least 2 count within 500 ms... Took: {sw.Elapsed:g}"); - - Task.Run(async () => - { - await SystemClock.SleepAsync(TimeSpan.FromMilliseconds(50)); - metrics.Counter(CounterName, 2); - }); - - sw.Restart(); - task = metrics.WaitForCounterAsync(CounterName, 2, TimeSpan.FromMilliseconds(500)); - await SystemClock.SleepAsync(TimeSpan.FromMilliseconds(100)); - Assert.True(await task, $"Expected at least 4 count within 500 ms... Took: {sw.Elapsed:g}"); - - using (var timeoutCancellationTokenSource = new CancellationTokenSource(500)) - { - sw.Restart(); - task = metrics.WaitForCounterAsync(CounterName, () => - { - metrics.Counter(CounterName); - return Task.CompletedTask; - }, cancellationToken: timeoutCancellationTokenSource.Token); - await SystemClock.SleepAsync(TimeSpan.FromMilliseconds(500)); - Assert.True(await task, $"Expected at least 5 count within 500 ms... Took: {sw.Elapsed:g}"); - } - - if (_logger.IsEnabled(LogLevel.Information)) - _logger.LogInformation((await metrics.GetCounterStatsAsync(CounterName)).ToString()); - } -#pragma warning restore 4014 - - public virtual async Task CanSendBufferedMetricsAsync() - { - using var metrics = GetMetricsClient(true) as IBufferedMetricsClient; - - if (metrics is not IMetricsClientStats stats) - return; - - Parallel.For(0, 100, i => metrics.Counter("c1")); - - await metrics.FlushAsync(); - - var counter = await stats.GetCounterStatsAsync("c1"); - Assert.Equal(100, counter.Count); - } -} - -#pragma warning restore 612, 618 diff --git a/src/Foundatio.TestHarness/Queue/QueueTestBase.cs b/src/Foundatio.TestHarness/Queue/QueueTestBase.cs index cfbdee9cb..b88ad7384 100644 --- a/src/Foundatio.TestHarness/Queue/QueueTestBase.cs +++ b/src/Foundatio.TestHarness/Queue/QueueTestBase.cs @@ -10,7 +10,6 @@ using Foundatio.Jobs; using Foundatio.Lock; using Foundatio.Messaging; -using Foundatio.Metrics; using Foundatio.Queues; using Foundatio.Tests.Extensions; using Foundatio.Tests.Metrics; @@ -28,14 +27,10 @@ public abstract class QueueTestBase : TestWithLoggingBase, IDisposable protected QueueTestBase(ITestOutputHelper output) : base(output) { Log.SetLogLevel(LogLevel.Debug); - Log.SetLogLevel(LogLevel.Debug); -#pragma warning disable CS0618 // Type or member is obsolete - Log.SetLogLevel>(LogLevel.Debug); -#pragma warning restore CS0618 // Type or member is obsolete Log.SetLogLevel(LogLevel.Debug); } - protected virtual IQueue GetQueue(int retries = 1, TimeSpan? workItemTimeout = null, TimeSpan? retryDelay = null, int[] retryMultipliers = null, int deadLetterMaxItems = 100, bool runQueueMaintenance = true) + protected virtual IQueue GetQueue(int retries = 1, TimeSpan? workItemTimeout = null, TimeSpan? retryDelay = null, int[] retryMultipliers = null, int deadLetterMaxItems = 100, bool runQueueMaintenance = true, TimeProvider timeProvider = null) { return null; } @@ -99,17 +94,17 @@ await queue.EnqueueAsync(new SimpleWorkItem Assert.Equal(1, stats.Completed); Assert.Equal(0, stats.Queued); - Assert.Equal(1, metricsCollector.GetSum("foundatio.simpleworkitem.enqueued")); - Assert.Equal(1, metricsCollector.GetSum("foundatio.simpleworkitem.dequeued")); - Assert.Equal(1, metricsCollector.GetSum("foundatio.simpleworkitem.completed")); + Assert.Equal(1, metricsCollector.GetSum("foundatio.simpleworkitem.enqueued")); + Assert.Equal(1, metricsCollector.GetSum("foundatio.simpleworkitem.dequeued")); + Assert.Equal(1, metricsCollector.GetSum("foundatio.simpleworkitem.completed")); - Assert.Equal(0, metricsCollector.GetSum("foundatio.simpleworkitem.count")); - Assert.Equal(0, metricsCollector.GetSum("foundatio.simpleworkitem.working")); - Assert.Equal(0, metricsCollector.GetSum("foundatio.simpleworkitem.deadletter")); + Assert.Equal(0, metricsCollector.GetSum("foundatio.simpleworkitem.count")); + Assert.Equal(0, metricsCollector.GetSum("foundatio.simpleworkitem.working")); + Assert.Equal(0, metricsCollector.GetSum("foundatio.simpleworkitem.deadletter")); - Assert.Equal(1, metricsCollector.GetSum("foundatio.simpleworkitem.myitem.enqueued")); - Assert.Equal(1, metricsCollector.GetSum("foundatio.simpleworkitem.myitem.dequeued")); - Assert.Equal(1, metricsCollector.GetSum("foundatio.simpleworkitem.myitem.completed")); + Assert.Equal(1, metricsCollector.GetSum("foundatio.simpleworkitem.myitem.enqueued")); + Assert.Equal(1, metricsCollector.GetSum("foundatio.simpleworkitem.myitem.dequeued")); + Assert.Equal(1, metricsCollector.GetSum("foundatio.simpleworkitem.myitem.completed")); } } finally @@ -157,9 +152,9 @@ await queue.EnqueueAsync(new SimpleWorkItem Assert.Equal(0, stats.Queued); metricsCollector.RecordObservableInstruments(); - Assert.Equal(1, metricsCollector.GetSum("foundatio.simpleworkitem.enqueued")); - Assert.Equal(1, metricsCollector.GetSum("foundatio.simpleworkitem.dequeued")); - Assert.Equal(1, metricsCollector.GetSum("foundatio.simpleworkitem.completed")); + Assert.Equal(1, metricsCollector.GetSum("foundatio.simpleworkitem.enqueued")); + Assert.Equal(1, metricsCollector.GetSum("foundatio.simpleworkitem.dequeued")); + Assert.Equal(1, metricsCollector.GetSum("foundatio.simpleworkitem.completed")); Assert.Equal(0, metricsCollector.GetSum("foundatio.simpleworkitem.count")); Assert.Equal(0, metricsCollector.GetSum("foundatio.simpleworkitem.working")); @@ -218,7 +213,7 @@ await queue.EnqueueAsync(new SimpleWorkItem if (_assertStats) { Assert.Equal(1, (await queue.GetQueueStatsAsync()).Dequeued); - Assert.Equal(1, metricsCollector.GetSum("foundatio.simpleworkitem.dequeued")); + Assert.Equal(1, metricsCollector.GetSum("foundatio.simpleworkitem.dequeued")); } await workItem.AbandonAsync(); @@ -234,9 +229,9 @@ await queue.EnqueueAsync(new SimpleWorkItem Assert.Equal(1, stats.Queued); metricsCollector.RecordObservableInstruments(); - Assert.Equal(0, metricsCollector.GetSum("foundatio.simpleworkitem.completed")); - Assert.Equal(1, metricsCollector.GetSum("foundatio.simpleworkitem.abandoned")); - Assert.Equal(1, metricsCollector.GetSum("foundatio.simpleworkitem.count")); + Assert.Equal(0, metricsCollector.GetSum("foundatio.simpleworkitem.completed")); + Assert.Equal(1, metricsCollector.GetSum("foundatio.simpleworkitem.abandoned")); + Assert.Equal(1, metricsCollector.GetMax("foundatio.simpleworkitem.count")); } workItem = await queue.DequeueAsync(TimeSpan.FromSeconds(10)); @@ -275,7 +270,7 @@ await queue.EnqueueAsync(new SimpleWorkItem if (_assertStats) { Assert.Equal(1, (await queue.GetQueueStatsAsync()).Enqueued); - Assert.Equal(1, metricsCollector.GetSum("foundatio.simpleworkitem.enqueued")); + Assert.Equal(1, metricsCollector.GetSum("foundatio.simpleworkitem.enqueued")); } await queue.EnqueueAsync(new SimpleWorkItem @@ -286,7 +281,7 @@ await queue.EnqueueAsync(new SimpleWorkItem if (_assertStats) { Assert.Equal(1, (await queue.GetQueueStatsAsync()).Enqueued); - Assert.Equal(1, metricsCollector.GetSum("foundatio.simpleworkitem.enqueued")); + Assert.Equal(1, metricsCollector.GetSum("foundatio.simpleworkitem.enqueued")); } var workItem = await queue.DequeueAsync(TimeSpan.Zero); @@ -295,7 +290,7 @@ await queue.EnqueueAsync(new SimpleWorkItem if (_assertStats) { Assert.Equal(1, (await queue.GetQueueStatsAsync()).Dequeued); - Assert.Equal(1, metricsCollector.GetSum("foundatio.simpleworkitem.dequeued")); + Assert.Equal(1, metricsCollector.GetSum("foundatio.simpleworkitem.dequeued")); } await queue.EnqueueAsync(new SimpleWorkItem @@ -306,7 +301,7 @@ await queue.EnqueueAsync(new SimpleWorkItem if (_assertStats) { Assert.Equal(2, (await queue.GetQueueStatsAsync()).Enqueued); - Assert.Equal(2, metricsCollector.GetSum("foundatio.simpleworkitem.enqueued")); + Assert.Equal(2, metricsCollector.GetSum("foundatio.simpleworkitem.enqueued")); } await workItem.CompleteAsync(); @@ -319,10 +314,10 @@ await queue.EnqueueAsync(new SimpleWorkItem Assert.Equal(1, stats.Queued); metricsCollector.RecordObservableInstruments(); - Assert.Equal(2, metricsCollector.GetSum("foundatio.simpleworkitem.enqueued")); - Assert.Equal(1, metricsCollector.GetSum("foundatio.simpleworkitem.dequeued")); - Assert.Equal(1, metricsCollector.GetSum("foundatio.simpleworkitem.completed")); - Assert.Equal(0, metricsCollector.GetSum("foundatio.simpleworkitem.abandoned")); + Assert.Equal(2, metricsCollector.GetSum("foundatio.simpleworkitem.enqueued")); + Assert.Equal(1, metricsCollector.GetSum("foundatio.simpleworkitem.dequeued")); + Assert.Equal(1, metricsCollector.GetSum("foundatio.simpleworkitem.completed")); + Assert.Equal(0, metricsCollector.GetSum("foundatio.simpleworkitem.abandoned")); Assert.Equal(1, metricsCollector.GetSum("foundatio.simpleworkitem.count")); Assert.Equal(0, metricsCollector.GetSum("foundatio.simpleworkitem.working")); @@ -339,7 +334,7 @@ await queue.EnqueueAsync(new SimpleWorkItem public virtual Task VerifyRetryAttemptsAsync() { const int retryCount = 2; - var queue = GetQueue(retryCount, TimeSpan.FromSeconds(1), TimeSpan.Zero, new[] { 1 }); + var queue = GetQueue(retryCount, TimeSpan.FromSeconds(1), TimeSpan.Zero, [1]); if (queue == null) return Task.CompletedTask; @@ -349,7 +344,7 @@ public virtual Task VerifyRetryAttemptsAsync() public virtual Task VerifyDelayedRetryAttemptsAsync() { const int retryCount = 2; - var queue = GetQueue(retryCount, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1), new[] { 1 }); + var queue = GetQueue(retryCount, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(1), [1]); if (queue == null) return Task.CompletedTask; @@ -404,9 +399,9 @@ await queue.EnqueueAsync(new SimpleWorkItem Assert.Equal(retryCount + 1, stats.Abandoned); metricsCollector.RecordObservableInstruments(); - Assert.Equal(retryCount + 1, metricsCollector.GetSum("foundatio.simpleworkitem.dequeued")); - Assert.Equal(0, metricsCollector.GetSum("foundatio.simpleworkitem.completed")); - Assert.Equal(retryCount + 1, metricsCollector.GetSum("foundatio.simpleworkitem.abandoned")); + Assert.Equal(retryCount + 1, metricsCollector.GetSum("foundatio.simpleworkitem.dequeued")); + Assert.Equal(0, metricsCollector.GetSum("foundatio.simpleworkitem.completed")); + Assert.Equal(retryCount + 1, metricsCollector.GetSum("foundatio.simpleworkitem.abandoned")); Assert.Equal(0, metricsCollector.GetSum("foundatio.simpleworkitem.count")); } @@ -442,7 +437,7 @@ await queue.EnqueueAsync(new SimpleWorkItem if (_assertStats) { Assert.Equal(1, (await queue.GetQueueStatsAsync()).Enqueued); - Assert.Equal(1, metricsCollector.GetSum("foundatio.simpleworkitem.enqueued")); + Assert.Equal(1, metricsCollector.GetSum("foundatio.simpleworkitem.enqueued")); } var workItem = await queue.DequeueAsync(new CancellationToken(true)); @@ -458,7 +453,7 @@ await queue.EnqueueAsync(new SimpleWorkItem var stats = await queue.GetQueueStatsAsync(); Assert.Equal(1, stats.Completed); Assert.Equal(0, stats.Queued); - Assert.Equal(1, metricsCollector.GetSum("foundatio.simpleworkitem.completed")); + Assert.Equal(1, metricsCollector.GetSum("foundatio.simpleworkitem.completed")); } } finally @@ -484,17 +479,12 @@ public virtual async Task CanDequeueEfficientlyAsync() using var metricsCollector = new DiagnosticsMetricsCollector(FoundatioDiagnostics.Meter.Name, _logger); - using var metrics = new InMemoryMetricsClient(new InMemoryMetricsClientOptions()); -#pragma warning disable CS0618 // Type or member is obsolete - queue.AttachBehavior(new MetricsQueueBehavior(metrics, reportCountsInterval: TimeSpan.FromMilliseconds(100), loggerFactory: Log)); -#pragma warning restore CS0618 // Type or member is obsolete - _ = Task.Run(async () => { _logger.LogTrace("Starting enqueue loop"); for (int index = 0; index < iterations; index++) { - await SystemClock.SleepAsync(RandomData.GetInt(10, 30)); + await Task.Delay(RandomData.GetInt(10, 30)); await queue.EnqueueAsync(new SimpleWorkItem { Data = "Hello" }); } _logger.LogTrace("Finished enqueuing"); @@ -509,10 +499,6 @@ public virtual async Task CanDequeueEfficientlyAsync() } _logger.LogTrace("Finished dequeuing"); - await metrics.FlushAsync(); - var timing = await metrics.GetTimerStatsAsync("foundatio.simpleworkitem.queuetime"); - if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("AverageDuration: {AverageDuration}", timing.AverageDuration); - Assert.InRange(timing.AverageDuration, 0, 75); Assert.InRange(metricsCollector.GetAvg("foundatio.simpleworkitem.queuetime"), 0, 75); } finally @@ -531,18 +517,15 @@ public virtual async Task CanResumeDequeueEfficientlyAsync() try { + using var metricsCollector = new DiagnosticsMetricsCollector(FoundatioDiagnostics.Meter.Name, _logger); + await queue.DeleteQueueAsync(); await AssertEmptyQueueAsync(queue); - using var metrics = new InMemoryMetricsClient(new InMemoryMetricsClientOptions()); - for (int index = 0; index < iterations; index++) await queue.EnqueueAsync(new SimpleWorkItem { Data = "Hello" }); using var secondQueue = GetQueue(runQueueMaintenance: false); -#pragma warning disable CS0618 // Type or member is obsolete - secondQueue.AttachBehavior(new MetricsQueueBehavior(metrics, reportCountsInterval: TimeSpan.FromMilliseconds(100), loggerFactory: Log)); -#pragma warning restore CS0618 // Type or member is obsolete _logger.LogTrace("Starting dequeue loop"); for (int index = 0; index < iterations; index++) @@ -553,10 +536,8 @@ public virtual async Task CanResumeDequeueEfficientlyAsync() await item.CompleteAsync(); } - await metrics.FlushAsync(); // This won't flush metrics queue behaviors - var timing = await metrics.GetTimerStatsAsync("simpleworkitem.queuetime"); - if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("TotalDuration: {TotalDuration} AverageDuration: {AverageDuration}", timing.TotalDuration, timing.AverageDuration); - Assert.InRange(timing.AverageDuration, 0, 75); + metricsCollector.RecordObservableInstruments(); + Assert.InRange(metricsCollector.GetAvg("foundatio.simpleworkitem.queuetime"), 0, 75); } finally { @@ -659,7 +640,7 @@ public virtual async Task WillWaitForItemAsync() _ = Task.Run(async () => { - await SystemClock.SleepAsync(500); + await Task.Delay(500); await queue.EnqueueAsync(new SimpleWorkItem { Data = "Hello" @@ -694,7 +675,7 @@ public virtual async Task DequeueWaitWillGetSignaledAsync() _ = Task.Run(async () => { - await SystemClock.SleepAsync(250); + await Task.Delay(250); await queue.EnqueueAsync(new SimpleWorkItem { Data = "Hello" @@ -767,11 +748,6 @@ public virtual async Task CanHandleErrorInWorkerAsync() await queue.DeleteQueueAsync(); await AssertEmptyQueueAsync(queue); - using var metrics = new InMemoryMetricsClient(new InMemoryMetricsClientOptions { Buffered = false, LoggerFactory = Log }); - -#pragma warning disable CS0618 // Type or member is obsolete - queue.AttachBehavior(new MetricsQueueBehavior(metrics, reportCountsInterval: TimeSpan.FromMilliseconds(100), loggerFactory: Log)); -#pragma warning restore CS0618 // Type or member is obsolete await queue.StartWorkingAsync(w => { _logger.LogDebug("WorkAction"); @@ -785,7 +761,7 @@ await queue.StartWorkingAsync(w => await queue.EnqueueAsync(new SimpleWorkItem { Data = "Hello" }); await resetEvent.WaitAsync(TimeSpan.FromSeconds(200)); - await SystemClock.SleepAsync(100); // give time for the stats to reflect the changes. + await Task.Delay(100); // give time for the stats to reflect the changes. if (_assertStats) { @@ -982,7 +958,7 @@ await Parallel.ForEachAsync(Enumerable.Range(1, workItemCount), cancellationToke }); await countdown.WaitAsync(cancellationTokenSource.Token); - await SystemClock.SleepAsync(50, cancellationTokenSource.Token); + await Task.Delay(50, cancellationTokenSource.Token); if (_logger.IsEnabled(LogLevel.Information)) _logger.LogInformation("Work Info Stats: Completed: {Completed} Abandoned: {Abandoned} Error: {Errors}", info.CompletedCount, info.AbandonCount, info.ErrorCount); @@ -1053,12 +1029,12 @@ await queue.EnqueueAsync(new SimpleWorkItem Assert.NotNull(workItem); Assert.Equal("Hello", workItem.Value.Data); - var startTime = SystemClock.UtcNow; + var startTime = DateTime.UtcNow; await workItem.AbandonAsync(); Assert.Equal(1, (await queue.GetQueueStatsAsync()).Abandoned); workItem = await queue.DequeueAsync(TimeSpan.FromSeconds(5)); - var elapsed = SystemClock.UtcNow.Subtract(startTime); + var elapsed = DateTime.UtcNow.Subtract(startTime); if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("Time {Elapsed}", elapsed); Assert.NotNull(workItem); Assert.True(elapsed > TimeSpan.FromSeconds(.95)); @@ -1077,12 +1053,7 @@ public virtual async Task CanRunWorkItemWithMetricsAsync() { int completedCount = 0; - using var metrics = new InMemoryMetricsClient(o => o.Buffered(false).LoggerFactory(Log)); - -#pragma warning disable CS0618 // Type or member is obsolete - var behavior = new MetricsQueueBehavior(metrics, "metric", TimeSpan.FromMilliseconds(100), loggerFactory: Log); -#pragma warning restore CS0618 // Type or member is obsolete - using var queue = new InMemoryQueue(o => o.Behaviors(behavior).LoggerFactory(Log)); + using var queue = new InMemoryQueue(o => o.LoggerFactory(Log)); Task Handler(object sender, CompletedEventArgs e) { @@ -1090,6 +1061,8 @@ Task Handler(object sender, CompletedEventArgs e) return Task.CompletedTask; } + using var metricsCollector = new DiagnosticsMetricsCollector(FoundatioDiagnostics.Meter.Name, _logger); + using (queue.Completed.AddHandler(Handler)) { _logger.LogTrace("Before enqueue"); @@ -1097,64 +1070,49 @@ Task Handler(object sender, CompletedEventArgs e) await queue.EnqueueAsync(new SimpleWorkItem { Id = 2, Data = "Testing" }); await queue.EnqueueAsync(new SimpleWorkItem { Id = 3, Data = "Testing" }); - await SystemClock.SleepAsync(100); + await Task.Delay(100); _logger.LogTrace("Before dequeue"); var item = await queue.DequeueAsync(); + await Task.Delay(100); await item.CompleteAsync(); item = await queue.DequeueAsync(); + await Task.Delay(100); await item.CompleteAsync(); item = await queue.DequeueAsync(); + await Task.Delay(100); await item.AbandonAsync(); _logger.LogTrace("Before asserts"); Assert.Equal(2, completedCount); - await SystemClock.SleepAsync(100); // flush metrics queue behaviors - await metrics.FlushAsync(); - Assert.InRange((await metrics.GetGaugeStatsAsync("metric.workitemdata.count")).Max, 1, 3); - Assert.InRange((await metrics.GetGaugeStatsAsync("metric.workitemdata.working")).Max, 0, 1); - - Assert.Equal(3, await metrics.GetCounterCountAsync("metric.workitemdata.simple.enqueued")); - Assert.Equal(3, await metrics.GetCounterCountAsync("metric.workitemdata.enqueued")); + metricsCollector.RecordObservableInstruments(); + Assert.InRange(metricsCollector.GetMax("foundatio.workitemdata.count"), 1, 3); + Assert.InRange(metricsCollector.GetMax("foundatio.workitemdata.working"), 0, 1); - Assert.Equal(3, await metrics.GetCounterCountAsync("metric.workitemdata.simple.dequeued")); - Assert.Equal(3, await metrics.GetCounterCountAsync("metric.workitemdata.dequeued")); + Assert.Equal(3, metricsCollector.GetCount("foundatio.workitemdata.simple.enqueued")); + Assert.Equal(3, metricsCollector.GetCount("foundatio.workitemdata.enqueued")); - Assert.Equal(2, await metrics.GetCounterCountAsync("metric.workitemdata.simple.completed")); - Assert.Equal(2, await metrics.GetCounterCountAsync("metric.workitemdata.completed")); + Assert.Equal(3, metricsCollector.GetCount("foundatio.workitemdata.simple.dequeued")); + Assert.Equal(3, metricsCollector.GetCount("foundatio.workitemdata.dequeued")); - Assert.Equal(1, await metrics.GetCounterCountAsync("metric.workitemdata.simple.abandoned")); - Assert.Equal(1, await metrics.GetCounterCountAsync("metric.workitemdata.abandoned")); + Assert.Equal(2, metricsCollector.GetCount("foundatio.workitemdata.simple.completed")); + Assert.Equal(2, metricsCollector.GetCount("foundatio.workitemdata.completed")); - var queueTiming = await metrics.GetTimerStatsAsync("metric.workitemdata.simple.queuetime"); - Assert.Equal(3, queueTiming.Count); - queueTiming = await metrics.GetTimerStatsAsync("metric.workitemdata.queuetime"); - Assert.Equal(3, queueTiming.Count); + Assert.Equal(1, metricsCollector.GetCount("foundatio.workitemdata.simple.abandoned")); + Assert.Equal(1, metricsCollector.GetCount("foundatio.workitemdata.abandoned")); - var processTiming = await metrics.GetTimerStatsAsync("metric.workitemdata.simple.processtime"); - Assert.Equal(3, processTiming.Count); - processTiming = await metrics.GetTimerStatsAsync("metric.workitemdata.processtime"); - Assert.Equal(3, processTiming.Count); + var measurements = metricsCollector.GetMeasurements("foundatio.workitemdata.simple.queuetime"); + Assert.Equal(3, measurements.Count); + measurements = metricsCollector.GetMeasurements("foundatio.workitemdata.queuetime"); + Assert.Equal(3, measurements.Count); - if (_assertStats) - { - var queueStats = await metrics.GetQueueStatsAsync("metric.workitemdata"); - Assert.Equal(3, queueStats.Enqueued.Count); - Assert.Equal(3, queueStats.Dequeued.Count); - Assert.Equal(2, queueStats.Completed.Count); - Assert.Equal(1, queueStats.Abandoned.Count); - Assert.InRange(queueStats.Count.Max, 1, 3); - Assert.InRange(queueStats.Working.Max, 0, 1); - - var subQueueStats = await metrics.GetQueueStatsAsync("metric.workitemdata", "simple"); - Assert.Equal(3, subQueueStats.Enqueued.Count); - Assert.Equal(3, subQueueStats.Dequeued.Count); - Assert.Equal(2, subQueueStats.Completed.Count); - Assert.Equal(1, subQueueStats.Abandoned.Count); - } + measurements = metricsCollector.GetMeasurements("foundatio.workitemdata.simple.processtime"); + Assert.Equal(3, measurements.Count); + measurements = metricsCollector.GetMeasurements("foundatio.workitemdata.processtime"); + Assert.Equal(3, measurements.Count); } } @@ -1185,11 +1143,11 @@ await queue.EnqueueAsync(new SimpleWorkItem Assert.Equal("Hello", entry.Value.Data); if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("Waiting for {RenewWait:g} before renewing lock", renewWait); - await SystemClock.SleepAsync(renewWait); + await Task.Delay(renewWait); _logger.LogTrace("Renewing lock"); await entry.RenewLockAsync(); if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("Waiting for {RenewWait:g} to see if lock was renewed", renewWait); - await SystemClock.SleepAsync(renewWait); + await Task.Delay(renewWait); // We shouldn't get another item here if RenewLock works. if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("Attempting to dequeue item that shouldn't exist"); @@ -1317,7 +1275,7 @@ public virtual async Task CanDequeueWithLockingAsync() using var cache = new InMemoryCacheClient(o => o.LoggerFactory(Log)); using var messageBus = new InMemoryMessageBus(o => o.LoggerFactory(Log)); - var distributedLock = new CacheLockProvider(cache, messageBus, Log); + var distributedLock = new CacheLockProvider(cache, messageBus, null, Log); await CanDequeueWithLockingImpAsync(distributedLock); } @@ -1333,12 +1291,6 @@ protected async Task CanDequeueWithLockingImpAsync(CacheLockProvider distributed await queue.DeleteQueueAsync(); await AssertEmptyQueueAsync(queue); - using var metrics = new InMemoryMetricsClient(new InMemoryMetricsClientOptions { Buffered = false, LoggerFactory = Log }); - -#pragma warning disable CS0618 // Type or member is obsolete - queue.AttachBehavior(new MetricsQueueBehavior(metrics, loggerFactory: Log)); -#pragma warning restore CS0618 // Type or member is obsolete - var resetEvent = new AsyncAutoResetEvent(); await queue.StartWorkingAsync(async w => { @@ -1346,7 +1298,7 @@ await queue.StartWorkingAsync(async w => var l = await distributedLock.AcquireAsync("test"); Assert.NotNull(l); _logger.LogInformation("Acquired distributed lock"); - await SystemClock.SleepAsync(TimeSpan.FromMilliseconds(250)); + await Task.Delay(TimeSpan.FromMilliseconds(250)); await l.ReleaseAsync(); _logger.LogInformation("Released distributed lock"); @@ -1359,7 +1311,7 @@ await queue.StartWorkingAsync(async w => if (_assertStats) { - await SystemClock.SleepAsync(1); + await Task.Delay(1); var stats = await queue.GetQueueStatsAsync(); _logger.LogInformation("Completed: {Completed} Errors: {Errors} Deadletter: {Deadletter} Working: {Working} ", stats.Completed, stats.Errors, stats.Deadletter, stats.Working); Assert.Equal(1, stats.Completed); @@ -1377,7 +1329,7 @@ public virtual async Task CanHaveMultipleQueueInstancesWithLockingAsync() using var cache = new InMemoryCacheClient(o => o.LoggerFactory(Log)); using var messageBus = new InMemoryMessageBus(o => o.LoggerFactory(Log)); - var distributedLock = new CacheLockProvider(cache, messageBus, Log); + var distributedLock = new CacheLockProvider(cache, messageBus, null, Log); await CanHaveMultipleQueueInstancesWithLockingImplAsync(distributedLock); } @@ -1413,7 +1365,7 @@ await q.StartWorkingAsync(async w => Assert.NotNull(l); if (_logger.IsEnabled(LogLevel.Information)) _logger.LogInformation("[{Instance}] Acquired distributed lock: {Id}", instanceCount, w.Id); - await SystemClock.SleepAsync(TimeSpan.FromMilliseconds(50), cancellationTokenSource.Token); + await Task.Delay(TimeSpan.FromMilliseconds(50), cancellationTokenSource.Token); await l.ReleaseAsync(); if (_logger.IsEnabled(LogLevel.Information)) _logger.LogInformation("[{Instance}] Released distributed lock: {Id}", instanceCount, w.Id); @@ -1438,7 +1390,7 @@ await Parallel.ForEachAsync(Enumerable.Range(1, workItemCount), cancellationToke }); await countdown.WaitAsync(TimeSpan.FromSeconds(5)); - await SystemClock.SleepAsync(50, cancellationTokenSource.Token); + await Task.Delay(50, cancellationTokenSource.Token); if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("Completed: {Completed} Abandoned: {Abandoned} Error: {Errors}", info.CompletedCount, info.AbandonCount, info.ErrorCount); if (_logger.IsEnabled(LogLevel.Information)) @@ -1552,7 +1504,7 @@ public virtual async Task MaintainJobNotAbandon_NotWorkTimeOutEntry() var dequeuedQueueItem = Assert.IsType>(await queue.DequeueAsync()); Assert.NotNull(dequeuedQueueItem.Value); // The first dequeued item works for 60 milliseconds less than work timeout(100 milliseconds). - await SystemClock.SleepAsync(60); + await Task.Delay(60); await dequeuedQueueItem.CompleteAsync(); Assert.True(dequeuedQueueItem.IsCompleted); Assert.False(dequeuedQueueItem.IsAbandoned); @@ -1560,7 +1512,7 @@ public virtual async Task MaintainJobNotAbandon_NotWorkTimeOutEntry() dequeuedQueueItem = Assert.IsType>(await queue.DequeueAsync()); Assert.NotNull(dequeuedQueueItem.Value); // The second dequeued item works for 60 milliseconds less than work timeout(100 milliseconds). - await SystemClock.SleepAsync(60); + await Task.Delay(60); await dequeuedQueueItem.CompleteAsync(); Assert.True(dequeuedQueueItem.IsCompleted); Assert.False(dequeuedQueueItem.IsAbandoned); diff --git a/src/Foundatio.TestHarness/Storage/FileStorageTestsBase.cs b/src/Foundatio.TestHarness/Storage/FileStorageTestsBase.cs index 87abd1546..fd813d367 100644 --- a/src/Foundatio.TestHarness/Storage/FileStorageTestsBase.cs +++ b/src/Foundatio.TestHarness/Storage/FileStorageTestsBase.cs @@ -144,17 +144,15 @@ public virtual async Task CanGetFileInfoAsync() var fileInfo = await storage.GetFileInfoAsync(Guid.NewGuid().ToString()); Assert.Null(fileInfo); - var startTime = SystemClock.UtcNow.Subtract(TimeSpan.FromMinutes(1)); + var startTime = DateTime.UtcNow.Subtract(TimeSpan.FromMinutes(1)); string path = $"folder\\{Guid.NewGuid()}-nested.txt"; Assert.True(await storage.SaveFileAsync(path, "test")); fileInfo = await storage.GetFileInfoAsync(path); Assert.NotNull(fileInfo); Assert.True(fileInfo.Path.EndsWith("nested.txt"), "Incorrect file"); Assert.True(fileInfo.Size > 0, "Incorrect file size"); - Assert.Equal(DateTimeKind.Utc, fileInfo.Created.Kind); // NOTE: File creation time might not be accurate: http://stackoverflow.com/questions/2109152/unbelievable-strange-file-creation-time-problem Assert.True(fileInfo.Created > DateTime.MinValue, "File creation time should be newer than the start time"); - Assert.Equal(DateTimeKind.Utc, fileInfo.Modified.Kind); Assert.True(startTime <= fileInfo.Modified, $"File {path} modified time {fileInfo.Modified:O} should be newer than the start time {startTime:O}."); path = $"{Guid.NewGuid()}-test.txt"; @@ -163,9 +161,7 @@ public virtual async Task CanGetFileInfoAsync() Assert.NotNull(fileInfo); Assert.True(fileInfo.Path.EndsWith("test.txt"), "Incorrect file"); Assert.True(fileInfo.Size > 0, "Incorrect file size"); - Assert.Equal(DateTimeKind.Utc, fileInfo.Created.Kind); Assert.True(fileInfo.Created > DateTime.MinValue, "File creation time should be newer than the start time."); - Assert.Equal(DateTimeKind.Utc, fileInfo.Modified.Kind); Assert.True(startTime <= fileInfo.Modified, $"File {path} modified time {fileInfo.Modified:O} should be newer than the start time {startTime:O}."); } } @@ -588,7 +584,7 @@ await Parallel.ForEachAsync(Enumerable.Range(1, 10), async (_, _) => if (RandomData.GetBool()) { - await storage.CompleteEventPostAsync(path, eventPost.ProjectId, SystemClock.UtcNow, true, _logger); + await storage.CompleteEventPostAsync(path, eventPost.ProjectId, DateTime.UtcNow, true, _logger); } else await storage.SetNotActiveAsync(path, _logger); diff --git a/src/Foundatio.Xunit/Foundatio.Xunit.csproj b/src/Foundatio.Xunit/Foundatio.Xunit.csproj index 5b9748438..aae731308 100644 --- a/src/Foundatio.Xunit/Foundatio.Xunit.csproj +++ b/src/Foundatio.Xunit/Foundatio.Xunit.csproj @@ -7,8 +7,8 @@ - - - + + + diff --git a/src/Foundatio.Xunit/Logging/TestLoggerOptions.cs b/src/Foundatio.Xunit/Logging/TestLoggerOptions.cs index 1929ea603..cfd77d8b0 100644 --- a/src/Foundatio.Xunit/Logging/TestLoggerOptions.cs +++ b/src/Foundatio.Xunit/Logging/TestLoggerOptions.cs @@ -1,5 +1,4 @@ using System; -using Foundatio.Utility; using Microsoft.Extensions.Logging; using Xunit.Abstractions; @@ -11,6 +10,7 @@ public class TestLoggerOptions public int MaxLogEntriesToStore { get; set; } = 100; public int MaxLogEntriesToWrite { get; set; } = 1000; public bool IncludeScopes { get; set; } = true; + public TimeProvider TimeProvider { get; set; } = TimeProvider.System; public void UseOutputHelper(Func getOutputHelper, Func formatLogEntry = null) { @@ -25,5 +25,5 @@ public void UseOutputHelper(Func getOutputHelper, Func WriteLogEntryFunc?.Invoke(logEntry); public Func NowFunc { get; set; } - internal DateTimeOffset GetNow() => NowFunc?.Invoke() ?? SystemClock.OffsetNow; + internal DateTimeOffset GetNow() => NowFunc?.Invoke() ?? TimeProvider.GetUtcNow(); } diff --git a/src/Foundatio/Caching/HybridCacheClient.cs b/src/Foundatio/Caching/HybridCacheClient.cs index b71bcd962..55d198652 100644 --- a/src/Foundatio/Caching/HybridCacheClient.cs +++ b/src/Foundatio/Caching/HybridCacheClient.cs @@ -12,7 +12,7 @@ namespace Foundatio.Caching; public interface IHybridCacheClient : ICacheClient { } -public class HybridCacheClient : IHybridCacheClient +public class HybridCacheClient : IHybridCacheClient, IHaveTimeProvider, IHaveLogger { protected readonly ICacheClient _distributedCache; protected readonly IMessageBus _messageBus; @@ -38,6 +38,9 @@ public HybridCacheClient(ICacheClient distributedCacheClient, IMessageBus messag public long LocalCacheHits => _localCacheHits; public long InvalidateCacheCalls => _invalidateCacheCalls; + ILogger IHaveLogger.Logger => _logger; + TimeProvider IHaveTimeProvider.TimeProvider => _distributedCache.GetTimeProvider(); + private Task OnLocalCacheItemExpiredAsync(object sender, ItemExpiredEventArgs args) { if (!args.SendNotification) diff --git a/src/Foundatio/Caching/InMemoryCacheClient.cs b/src/Foundatio/Caching/InMemoryCacheClient.cs index d6c003361..73d4732b7 100644 --- a/src/Foundatio/Caching/InMemoryCacheClient.cs +++ b/src/Foundatio/Caching/InMemoryCacheClient.cs @@ -13,7 +13,7 @@ namespace Foundatio.Caching; -public class InMemoryCacheClient : IMemoryCacheClient +public class InMemoryCacheClient : IMemoryCacheClient, IHaveTimeProvider, IHaveLogger { private readonly ConcurrentDictionary _memory; private readonly bool _shouldClone; @@ -22,6 +22,7 @@ public class InMemoryCacheClient : IMemoryCacheClient private long _writes; private long _hits; private long _misses; + private readonly TimeProvider _timeProvider; private readonly ILogger _logger; private readonly AsyncLock _lock = new(); @@ -34,6 +35,7 @@ public InMemoryCacheClient(InMemoryCacheClientOptions options = null) _shouldClone = options.CloneValues; _shouldThrowOnSerializationErrors = options.ShouldThrowOnSerializationError; _maxItems = options.MaxItems; + _timeProvider = options.TimeProvider ?? TimeProvider.System; var loggerFactory = options.LoggerFactory ?? NullLoggerFactory.Instance; _logger = loggerFactory.CreateLogger(); _memory = new ConcurrentDictionary(); @@ -50,6 +52,9 @@ public InMemoryCacheClient(Builder _hits; public long Misses => _misses; + ILogger IHaveLogger.Logger => _logger; + TimeProvider IHaveTimeProvider.TimeProvider => _timeProvider; + public override string ToString() { return $"Count: {Count} Calls: {Calls} Reads: {Reads} Writes: {Writes} Hits: {Hits} Misses: {Misses}"; @@ -196,11 +201,11 @@ public Task RemoveByPrefixAsync(string prefix) internal void RemoveExpiredKey(string key, bool sendNotification = true) { // Consideration: We could reduce the amount of calls to this by updating ExpiresAt and only having maintenance remove keys. - if (_memory.TryGetValue(key, out var existingEntry) && existingEntry.ExpiresAt < SystemClock.UtcNow) + if (_memory.TryGetValue(key, out var existingEntry) && existingEntry.ExpiresAt < _timeProvider.GetUtcNow()) { if (_memory.TryRemove(key, out var removedEntry)) { - if (removedEntry.ExpiresAt >= SystemClock.UtcNow) + if (removedEntry.ExpiresAt >= _timeProvider.GetUtcNow()) throw new Exception("Removed item was not expired"); _logger.LogDebug("Removing expired cache entry {Key}", key); @@ -220,7 +225,7 @@ public Task> GetAsync(string key) return Task.FromResult(CacheValue.NoValue); } - if (existingEntry.ExpiresAt < SystemClock.UtcNow) + if (existingEntry.ExpiresAt < _timeProvider.GetUtcNow().UtcDateTime) { Interlocked.Increment(ref _misses); return Task.FromResult(CacheValue.NoValue); @@ -260,8 +265,8 @@ public Task AddAsync(string key, T value, TimeSpan? expiresIn = null) if (String.IsNullOrEmpty(key)) return Task.FromException(new ArgumentNullException(nameof(key), "Key cannot be null or empty.")); - var expiresAt = expiresIn.HasValue ? SystemClock.UtcNow.SafeAdd(expiresIn.Value) : DateTime.MaxValue; - return SetInternalAsync(key, new CacheEntry(value, expiresAt, _shouldClone), true); + var expiresAt = expiresIn.HasValue ? _timeProvider.GetUtcNow().UtcDateTime.SafeAdd(expiresIn.Value) : DateTime.MaxValue; + return SetInternalAsync(key, new CacheEntry(value, expiresAt, _timeProvider, _shouldClone), true); } public Task SetAsync(string key, T value, TimeSpan? expiresIn = null) @@ -269,8 +274,8 @@ public Task SetAsync(string key, T value, TimeSpan? expiresIn = null) if (String.IsNullOrEmpty(key)) return Task.FromException(new ArgumentNullException(nameof(key), "Key cannot be null or empty.")); - var expiresAt = expiresIn.HasValue ? SystemClock.UtcNow.SafeAdd(expiresIn.Value) : DateTime.MaxValue; - return SetInternalAsync(key, new CacheEntry(value, expiresAt, _shouldClone)); + var expiresAt = expiresIn.HasValue ? _timeProvider.GetUtcNow().UtcDateTime.SafeAdd(expiresIn.Value) : DateTime.MaxValue; + return SetInternalAsync(key, new CacheEntry(value, expiresAt, _timeProvider, _shouldClone)); } public async Task SetIfHigherAsync(string key, double value, TimeSpan? expiresIn = null) @@ -287,8 +292,8 @@ public async Task SetIfHigherAsync(string key, double value, TimeSpan? e Interlocked.Increment(ref _writes); double difference = value; - var expiresAt = expiresIn.HasValue ? SystemClock.UtcNow.SafeAdd(expiresIn.Value) : DateTime.MaxValue; - _memory.AddOrUpdate(key, new CacheEntry(value, expiresAt, _shouldClone), (_, existingEntry) => + var expiresAt = expiresIn.HasValue ? _timeProvider.GetUtcNow().UtcDateTime.SafeAdd(expiresIn.Value) : DateTime.MaxValue; + _memory.AddOrUpdate(key, new CacheEntry(value, expiresAt, _timeProvider, _shouldClone), (_, existingEntry) => { double? currentValue = null; try @@ -335,8 +340,8 @@ public async Task SetIfHigherAsync(string key, long value, TimeSpan? expir Interlocked.Increment(ref _writes); long difference = value; - var expiresAt = expiresIn.HasValue ? SystemClock.UtcNow.SafeAdd(expiresIn.Value) : DateTime.MaxValue; - _memory.AddOrUpdate(key, new CacheEntry(value, expiresAt, _shouldClone), (_, existingEntry) => + var expiresAt = expiresIn.HasValue ? _timeProvider.GetUtcNow().UtcDateTime.SafeAdd(expiresIn.Value) : DateTime.MaxValue; + _memory.AddOrUpdate(key, new CacheEntry(value, expiresAt, _timeProvider, _shouldClone), (_, existingEntry) => { long? currentValue = null; try @@ -383,8 +388,8 @@ public async Task SetIfLowerAsync(string key, double value, TimeSpan? ex Interlocked.Increment(ref _writes); double difference = value; - var expiresAt = expiresIn.HasValue ? SystemClock.UtcNow.SafeAdd(expiresIn.Value) : DateTime.MaxValue; - _memory.AddOrUpdate(key, new CacheEntry(value, expiresAt, _shouldClone), (_, existingEntry) => + var expiresAt = expiresIn.HasValue ? _timeProvider.GetUtcNow().UtcDateTime.SafeAdd(expiresIn.Value) : DateTime.MaxValue; + _memory.AddOrUpdate(key, new CacheEntry(value, expiresAt, _timeProvider, _shouldClone), (_, existingEntry) => { double? currentValue = null; try @@ -429,8 +434,8 @@ public async Task SetIfLowerAsync(string key, long value, TimeSpan? expire } long difference = value; - var expiresAt = expiresIn.HasValue ? SystemClock.UtcNow.SafeAdd(expiresIn.Value) : DateTime.MaxValue; - _memory.AddOrUpdate(key, new CacheEntry(value, expiresAt, _shouldClone), (_, existingEntry) => + var expiresAt = expiresIn.HasValue ? _timeProvider.GetUtcNow().UtcDateTime.SafeAdd(expiresIn.Value) : DateTime.MaxValue; + _memory.AddOrUpdate(key, new CacheEntry(value, expiresAt, _timeProvider, _shouldClone), (_, existingEntry) => { long? currentValue = null; try @@ -471,8 +476,8 @@ public async Task ListAddAsync(string key, IEnumerable values, TimeS if (values == null) throw new ArgumentNullException(nameof(values)); - var expiresAt = expiresIn.HasValue ? SystemClock.UtcNow.SafeAdd(expiresIn.Value) : DateTime.MaxValue; - if (expiresAt < SystemClock.UtcNow) + var expiresAt = expiresIn.HasValue ? _timeProvider.GetUtcNow().UtcDateTime.SafeAdd(expiresIn.Value) : DateTime.MaxValue; + if (expiresAt < _timeProvider.GetUtcNow().UtcDateTime) { RemoveExpiredKey(key); return default; @@ -483,7 +488,7 @@ public async Task ListAddAsync(string key, IEnumerable values, TimeS if (values is string stringValue) { var items = new HashSet(new[] { stringValue }); - var entry = new CacheEntry(items, expiresAt, _shouldClone); + var entry = new CacheEntry(items, expiresAt, _timeProvider, _shouldClone); _memory.AddOrUpdate(key, entry, (existingKey, existingEntry) => { if (existingEntry.Value is not ICollection collection) @@ -505,7 +510,7 @@ public async Task ListAddAsync(string key, IEnumerable values, TimeS else { var items = new HashSet(values); - var entry = new CacheEntry(items, expiresAt, _shouldClone); + var entry = new CacheEntry(items, expiresAt, _timeProvider, _shouldClone); _memory.AddOrUpdate(key, entry, (existingKey, existingEntry) => { if (existingEntry.Value is not ICollection collection) @@ -534,8 +539,8 @@ public Task ListRemoveAsync(string key, IEnumerable values, TimeSpan if (values == null) throw new ArgumentNullException(nameof(values)); - var expiresAt = expiresIn.HasValue ? SystemClock.UtcNow.SafeAdd(expiresIn.Value) : DateTime.MaxValue; - if (expiresAt < SystemClock.UtcNow) + var expiresAt = expiresIn.HasValue ? _timeProvider.GetUtcNow().UtcDateTime.SafeAdd(expiresIn.Value) : DateTime.MaxValue; + if (expiresAt < _timeProvider.GetUtcNow().UtcDateTime) { RemoveExpiredKey(key); return default; @@ -608,7 +613,7 @@ private async Task SetInternalAsync(string key, CacheEntry entry, bool add if (String.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key), "SetInternalAsync: Key cannot be null or empty"); - if (entry.ExpiresAt < SystemClock.UtcNow) + if (entry.ExpiresAt < _timeProvider.GetUtcNow().UtcDateTime) { RemoveExpiredKey(key); return false; @@ -626,7 +631,7 @@ private async Task SetInternalAsync(string key, CacheEntry entry, bool add wasUpdated = false; // check to see if existing entry is expired - if (existingEntry.ExpiresAt < SystemClock.UtcNow) + if (existingEntry.ExpiresAt < _timeProvider.GetUtcNow().UtcDateTime) { if (isTraceLogLevelEnabled) _logger.LogTrace("Attempting to replacing expired cache key: {Key}", existingKey); @@ -685,7 +690,7 @@ public async Task ReplaceIfEqualAsync(string key, T value, T expected, Interlocked.Increment(ref _writes); - var expiresAt = expiresIn.HasValue ? SystemClock.UtcNow.SafeAdd(expiresIn.Value) : DateTime.MaxValue; + var expiresAt = expiresIn.HasValue ? _timeProvider.GetUtcNow().UtcDateTime.SafeAdd(expiresIn.Value) : DateTime.MaxValue; bool wasExpectedValue = false; bool success = _memory.TryUpdate(key, (_, existingEntry) => { @@ -724,8 +729,8 @@ public async Task IncrementAsync(string key, double amount, TimeSpan? ex Interlocked.Increment(ref _writes); - var expiresAt = expiresIn.HasValue ? SystemClock.UtcNow.SafeAdd(expiresIn.Value) : DateTime.MaxValue; - var result = _memory.AddOrUpdate(key, new CacheEntry(amount, expiresAt, _shouldClone), (_, existingEntry) => + var expiresAt = expiresIn.HasValue ? _timeProvider.GetUtcNow().UtcDateTime.SafeAdd(expiresIn.Value) : DateTime.MaxValue; + var result = _memory.AddOrUpdate(key, new CacheEntry(amount, expiresAt, _timeProvider, _shouldClone), (_, existingEntry) => { double? currentValue = null; try @@ -766,8 +771,8 @@ public async Task IncrementAsync(string key, long amount, TimeSpan? expire Interlocked.Increment(ref _writes); - var expiresAt = expiresIn.HasValue ? SystemClock.UtcNow.SafeAdd(expiresIn.Value) : DateTime.MaxValue; - var result = _memory.AddOrUpdate(key, new CacheEntry(amount, expiresAt, _shouldClone), (_, existingEntry) => + var expiresAt = expiresIn.HasValue ? _timeProvider.GetUtcNow().UtcDateTime.SafeAdd(expiresIn.Value) : DateTime.MaxValue; + var result = _memory.AddOrUpdate(key, new CacheEntry(amount, expiresAt, _timeProvider, _shouldClone), (_, existingEntry) => { long? currentValue = null; try @@ -806,7 +811,7 @@ public Task ExistsAsync(string key) return Task.FromResult(false); } - if (existingEntry.ExpiresAt < SystemClock.UtcNow) + if (existingEntry.ExpiresAt < _timeProvider.GetUtcNow().UtcDateTime) { Interlocked.Increment(ref _misses); return Task.FromResult(false); @@ -827,14 +832,14 @@ public Task ExistsAsync(string key) return Task.FromResult(null); } - if (existingEntry.ExpiresAt < SystemClock.UtcNow || existingEntry.ExpiresAt == DateTime.MaxValue) + if (existingEntry.ExpiresAt < _timeProvider.GetUtcNow().UtcDateTime || existingEntry.ExpiresAt == DateTime.MaxValue) { Interlocked.Increment(ref _misses); return Task.FromResult(null); } Interlocked.Increment(ref _hits); - return Task.FromResult(existingEntry.ExpiresAt.Subtract(SystemClock.UtcNow)); + return Task.FromResult(existingEntry.ExpiresAt.Subtract(_timeProvider.GetUtcNow().UtcDateTime)); } public async Task SetExpirationAsync(string key, TimeSpan expiresIn) @@ -842,8 +847,8 @@ public async Task SetExpirationAsync(string key, TimeSpan expiresIn) if (String.IsNullOrEmpty(key)) throw new ArgumentNullException(nameof(key), "Key cannot be null or empty"); - var expiresAt = SystemClock.UtcNow.SafeAdd(expiresIn); - if (expiresAt < SystemClock.UtcNow) + var expiresAt = _timeProvider.GetUtcNow().UtcDateTime.SafeAdd(expiresIn); + if (expiresAt < _timeProvider.GetUtcNow()) { RemoveExpiredKey(key); return; @@ -857,11 +862,11 @@ public async Task SetExpirationAsync(string key, TimeSpan expiresIn) } } - private DateTimeOffset _lastMaintenance; + private DateTime _lastMaintenance; private async Task StartMaintenanceAsync(bool compactImmediately = false) { - var now = SystemClock.UtcNow; + var now = _timeProvider.GetUtcNow().UtcDateTime; if (compactImmediately) await CompactAsync().AnyContext(); @@ -886,7 +891,7 @@ private async Task CompactAsync() (string Key, long LastAccessTicks, long InstanceNumber) oldest = (null, Int64.MaxValue, 0); foreach (var kvp in _memory) { - bool isExpired = kvp.Value.ExpiresAt < SystemClock.UtcNow; + bool isExpired = kvp.Value.ExpiresAt < _timeProvider.GetUtcNow().UtcDateTime; if (isExpired || kvp.Value.LastAccessTicks < oldest.LastAccessTicks || (kvp.Value.LastAccessTicks == oldest.LastAccessTicks && kvp.Value.InstanceNumber < oldest.InstanceNumber)) @@ -901,7 +906,7 @@ private async Task CompactAsync() _logger.LogDebug("Removing cache entry {Key} due to cache exceeding max item count limit", oldest); _memory.TryRemove(oldest.Key, out var cacheEntry); - if (cacheEntry != null && cacheEntry.ExpiresAt < SystemClock.UtcNow) + if (cacheEntry != null && cacheEntry.ExpiresAt < _timeProvider.GetUtcNow().UtcDateTime) expiredKey = oldest.Key; } @@ -913,7 +918,7 @@ private async Task DoMaintenanceAsync() { _logger.LogTrace("DoMaintenance"); - var utcNow = SystemClock.UtcNow.AddMilliseconds(50); + var utcNow = _timeProvider.GetUtcNow().AddMilliseconds(50); // Remove expired items and items that are infrequently accessed as they may be updated by add. long lastAccessMaximumTicks = utcNow.AddMilliseconds(-300).Ticks; @@ -949,16 +954,18 @@ private sealed record CacheEntry private object _cacheValue; private static long _instanceCount; private readonly bool _shouldClone; + private readonly TimeProvider _timeProvider; #if DEBUG private long _usageCount; #endif - public CacheEntry(object value, DateTime expiresAt, bool shouldClone = true) + public CacheEntry(object value, DateTime expiresAt, TimeProvider timeProvider, bool shouldClone = true) { + _timeProvider = timeProvider; _shouldClone = shouldClone && TypeRequiresCloning(value?.GetType()); Value = value; ExpiresAt = expiresAt; - LastModifiedTicks = SystemClock.UtcNow.Ticks; + LastModifiedTicks = _timeProvider.GetUtcNow().Ticks; InstanceNumber = Interlocked.Increment(ref _instanceCount); } @@ -974,7 +981,7 @@ internal object Value { get { - LastAccessTicks = SystemClock.UtcNow.Ticks; + LastAccessTicks = _timeProvider.GetUtcNow().Ticks; #if DEBUG Interlocked.Increment(ref _usageCount); #endif @@ -983,8 +990,8 @@ internal object Value set { _cacheValue = _shouldClone ? value.DeepClone() : value; - LastAccessTicks = SystemClock.UtcNow.Ticks; - LastModifiedTicks = SystemClock.UtcNow.Ticks; + LastAccessTicks = _timeProvider.GetUtcNow().Ticks; + LastModifiedTicks = _timeProvider.GetUtcNow().Ticks; } } diff --git a/src/Foundatio/Caching/ScopedCacheClient.cs b/src/Foundatio/Caching/ScopedCacheClient.cs index 880f4084a..ad60e01fd 100644 --- a/src/Foundatio/Caching/ScopedCacheClient.cs +++ b/src/Foundatio/Caching/ScopedCacheClient.cs @@ -3,6 +3,7 @@ using System.Linq; using System.Threading.Tasks; using Foundatio.Utility; +using Microsoft.Extensions.Logging; namespace Foundatio.Caching; @@ -11,7 +12,7 @@ public class ScopedHybridCacheClient : ScopedCacheClient, IHybridCacheClient public ScopedHybridCacheClient(IHybridCacheClient client, string scope = null) : base(client, scope) { } } -public class ScopedCacheClient : ICacheClient +public class ScopedCacheClient : ICacheClient, IHaveLogger, IHaveTimeProvider { private string _keyPrefix; private bool _isLocked; @@ -30,6 +31,9 @@ public ScopedCacheClient(ICacheClient client, string scope = null) public string Scope { get; private set; } + ILogger IHaveLogger.Logger => UnscopedCache.GetLogger(); + TimeProvider IHaveTimeProvider.TimeProvider => UnscopedCache.GetTimeProvider(); + public void SetScope(string scope) { if (_isLocked) diff --git a/src/Foundatio/Extensions/CacheClientExtensions.cs b/src/Foundatio/Extensions/CacheClientExtensions.cs index 76341a0f7..ace3a916b 100644 --- a/src/Foundatio/Extensions/CacheClientExtensions.cs +++ b/src/Foundatio/Extensions/CacheClientExtensions.cs @@ -21,12 +21,12 @@ public static Task>> GetAllAsync(this ICach public static Task IncrementAsync(this ICacheClient client, string key, long amount, DateTime? expiresAtUtc) { - return client.IncrementAsync(key, amount, expiresAtUtc?.Subtract(SystemClock.UtcNow)); + return client.IncrementAsync(key, amount, expiresAtUtc?.Subtract(client.GetTimeProvider().GetUtcNow().UtcDateTime)); } public static Task IncrementAsync(this ICacheClient client, string key, double amount, DateTime? expiresAtUtc) { - return client.IncrementAsync(key, amount, expiresAtUtc?.Subtract(SystemClock.UtcNow)); + return client.IncrementAsync(key, amount, expiresAtUtc?.Subtract(client.GetTimeProvider().GetUtcNow().UtcDateTime)); } public static Task IncrementAsync(this ICacheClient client, string key, TimeSpan? expiresIn = null) @@ -46,42 +46,42 @@ public static Task DecrementAsync(this ICacheClient client, string key, lo public static Task DecrementAsync(this ICacheClient client, string key, long amount, DateTime? expiresAtUtc) { - return client.IncrementAsync(key, -amount, expiresAtUtc?.Subtract(SystemClock.UtcNow)); + return client.IncrementAsync(key, -amount, expiresAtUtc?.Subtract(client.GetTimeProvider().GetUtcNow().UtcDateTime)); } public static Task DecrementAsync(this ICacheClient client, string key, double amount, DateTime? expiresAtUtc) { - return client.IncrementAsync(key, -amount, expiresAtUtc?.Subtract(SystemClock.UtcNow)); + return client.IncrementAsync(key, -amount, expiresAtUtc?.Subtract(client.GetTimeProvider().GetUtcNow().UtcDateTime)); } public static Task AddAsync(this ICacheClient client, string key, T value, DateTime? expiresAtUtc) { - return client.AddAsync(key, value, expiresAtUtc?.Subtract(SystemClock.UtcNow)); + return client.AddAsync(key, value, expiresAtUtc?.Subtract(client.GetTimeProvider().GetUtcNow().UtcDateTime)); } public static Task SetAsync(this ICacheClient client, string key, T value, DateTime? expiresAtUtc) { - return client.SetAsync(key, value, expiresAtUtc?.Subtract(SystemClock.UtcNow)); + return client.SetAsync(key, value, expiresAtUtc?.Subtract(client.GetTimeProvider().GetUtcNow().UtcDateTime)); } public static Task ReplaceAsync(this ICacheClient client, string key, T value, DateTime? expiresAtUtc) { - return client.ReplaceAsync(key, value, expiresAtUtc?.Subtract(SystemClock.UtcNow)); + return client.ReplaceAsync(key, value, expiresAtUtc?.Subtract(client.GetTimeProvider().GetUtcNow().UtcDateTime)); } public static Task ReplaceIfEqualAsync(this ICacheClient client, string key, T value, T expected, DateTime? expiresAtUtc) { - return client.ReplaceIfEqualAsync(key, value, expected, expiresAtUtc?.Subtract(SystemClock.UtcNow)); + return client.ReplaceIfEqualAsync(key, value, expected, expiresAtUtc?.Subtract(client.GetTimeProvider().GetUtcNow().UtcDateTime)); } public static Task SetAllAsync(this ICacheClient client, IDictionary values, DateTime? expiresAtUtc) { - return client.SetAllAsync(values, expiresAtUtc?.Subtract(SystemClock.UtcNow)); + return client.SetAllAsync(values, expiresAtUtc?.Subtract(client.GetTimeProvider().GetUtcNow().UtcDateTime)); } public static Task SetExpirationAsync(this ICacheClient client, string key, DateTime expiresAtUtc) { - return client.SetExpirationAsync(key, expiresAtUtc.Subtract(SystemClock.UtcNow)); + return client.SetExpirationAsync(key, expiresAtUtc.Subtract(client.GetTimeProvider().GetUtcNow().UtcDateTime)); } public static async Task ListAddAsync(this ICacheClient client, string key, T value, TimeSpan? expiresIn = null) @@ -152,10 +152,10 @@ public static Task SetUnixTimeMillisecondsAsync(this ICacheClient client, public static Task SetUnixTimeMillisecondsAsync(this ICacheClient client, string key, DateTime value, DateTime? expiresAtUtc) { - return client.SetAsync(key, value.ToUnixTimeMilliseconds(), expiresAtUtc?.Subtract(SystemClock.UtcNow)); + return client.SetAsync(key, value.ToUnixTimeMilliseconds(), expiresAtUtc?.Subtract(client.GetTimeProvider().GetUtcNow().UtcDateTime)); } - public static async Task GetUnixTimeSecondsAsync(this ICacheClient client, string key, DateTime? defaultValue = null) + public static async Task GetUnixTimeSecondsAsync(this ICacheClient client, string key, DateTime? defaultValue = null) { var unixTime = await client.GetAsync(key).AnyContext(); if (!unixTime.HasValue) @@ -171,6 +171,6 @@ public static Task SetUnixTimeSecondsAsync(this ICacheClient client, strin public static Task SetUnixTimeSecondsAsync(this ICacheClient client, string key, DateTime value, DateTime? expiresAtUtc) { - return client.SetAsync(key, value.ToUnixTimeSeconds(), expiresAtUtc?.Subtract(SystemClock.UtcNow)); + return client.SetAsync(key, value.ToUnixTimeSeconds(), expiresAtUtc?.Subtract(client.GetTimeProvider().GetUtcNow().UtcDateTime)); } } diff --git a/src/Foundatio/Extensions/DateTimeExtensions.cs b/src/Foundatio/Extensions/DateTimeExtensions.cs index 37a5090db..3e0aef7c9 100644 --- a/src/Foundatio/Extensions/DateTimeExtensions.cs +++ b/src/Foundatio/Extensions/DateTimeExtensions.cs @@ -29,9 +29,9 @@ public static long ToUnixTimeSeconds(this DateTime date) return new DateTimeOffset(date.ToUniversalTime()).ToUnixTimeSeconds(); } - public static DateTime FromUnixTimeSeconds(this long timestamp) + public static DateTimeOffset FromUnixTimeSeconds(this long timestamp) { - return DateTimeOffset.FromUnixTimeSeconds(timestamp).UtcDateTime; + return DateTimeOffset.FromUnixTimeSeconds(timestamp); } public static DateTime SafeAdd(this DateTime date, TimeSpan value) diff --git a/src/Foundatio/Extensions/TaskExtensions.cs b/src/Foundatio/Extensions/TaskExtensions.cs index 8178c0c8e..c0280f534 100644 --- a/src/Foundatio/Extensions/TaskExtensions.cs +++ b/src/Foundatio/Extensions/TaskExtensions.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Runtime.CompilerServices; +using System.Threading; using System.Threading.Tasks; using Foundatio.AsyncEx; @@ -44,4 +45,15 @@ public static ConfiguredTaskAwaitable AnyContext(this Awaitabl { return task.ConfigureAwait(continueOnCapturedContext: false); } + + public static async Task SafeDelay(this TimeProvider timeProvider, TimeSpan delay, CancellationToken cancellationToken = default) + { + try + { + await timeProvider.Delay(delay, cancellationToken); + } + catch (OperationCanceledException) + { + } + } } diff --git a/src/Foundatio/Foundatio.csproj b/src/Foundatio/Foundatio.csproj index 91824d2ee..0fa47d542 100644 --- a/src/Foundatio/Foundatio.csproj +++ b/src/Foundatio/Foundatio.csproj @@ -1,9 +1,10 @@ - - - - - + + + + + + diff --git a/src/Foundatio/Jobs/IJob.cs b/src/Foundatio/Jobs/IJob.cs index 77b1b4879..cb2b9f11f 100644 --- a/src/Foundatio/Jobs/IJob.cs +++ b/src/Foundatio/Jobs/IJob.cs @@ -63,11 +63,11 @@ public static async Task RunContinuousAsync(this IJob job, TimeSpan? interv if (result.Error != null) { - await SystemClock.SleepSafeAsync(Math.Max((int)(interval?.TotalMilliseconds ?? 0), 100), cancellationToken).AnyContext(); + await job.GetTimeProvider().SafeDelay(TimeSpan.FromMilliseconds(Math.Max((int)(interval?.TotalMilliseconds ?? 0), 100)), cancellationToken).AnyContext(); } else if (interval.HasValue && interval.Value > TimeSpan.Zero) { - await SystemClock.SleepSafeAsync(interval.Value, cancellationToken).AnyContext(); + await job.GetTimeProvider().SafeDelay(interval.Value, cancellationToken).AnyContext(); } // needed to yield back a task for jobs that aren't async diff --git a/src/Foundatio/Jobs/JobBase.cs b/src/Foundatio/Jobs/JobBase.cs index c26f86313..a19a0b598 100644 --- a/src/Foundatio/Jobs/JobBase.cs +++ b/src/Foundatio/Jobs/JobBase.cs @@ -7,17 +7,24 @@ namespace Foundatio.Jobs; -public abstract class JobBase : IJob, IHaveLogger +public abstract class JobBase : IJob, IHaveLogger, IHaveTimeProvider { + private readonly TimeProvider _timeProvider; protected readonly ILogger _logger; - public JobBase(ILoggerFactory loggerFactory = null) + public JobBase(ILoggerFactory loggerFactory = null) : this(null, loggerFactory) { + } + + public JobBase(TimeProvider timeProvider, ILoggerFactory loggerFactory = null) + { + _timeProvider = timeProvider ?? TimeProvider.System; _logger = loggerFactory?.CreateLogger(GetType()) ?? NullLogger.Instance; } public string JobId { get; } = Guid.NewGuid().ToString("N").Substring(0, 10); ILogger IHaveLogger.Logger => _logger; + TimeProvider IHaveTimeProvider.TimeProvider => _timeProvider; public virtual Task RunAsync(CancellationToken cancellationToken = default) { diff --git a/src/Foundatio/Jobs/JobRunner.cs b/src/Foundatio/Jobs/JobRunner.cs index 515100593..57f229f80 100644 --- a/src/Foundatio/Jobs/JobRunner.cs +++ b/src/Foundatio/Jobs/JobRunner.cs @@ -5,6 +5,7 @@ using System.Threading; using System.Threading.Tasks; using Foundatio.Utility; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; @@ -12,6 +13,7 @@ namespace Foundatio.Jobs; public class JobRunner { + private readonly TimeProvider _timeProvider; private readonly ILogger _logger; private string _jobName; private readonly JobOptions _options; @@ -19,6 +21,7 @@ public class JobRunner public JobRunner(JobOptions options, IServiceProvider serviceProvider, ILoggerFactory loggerFactory = null) { + _timeProvider = serviceProvider.GetService() ?? TimeProvider.System; _logger = loggerFactory?.CreateLogger() ?? NullLogger.Instance; _options = options; _serviceProvider = serviceProvider; @@ -165,33 +168,40 @@ public async Task RunAsync(CancellationToken cancellationToken = default) try { if (_options.InitialDelay.HasValue && _options.InitialDelay.Value > TimeSpan.Zero) - await SystemClock.SleepAsync(_options.InitialDelay.Value, cancellationToken).AnyContext(); + await _timeProvider.SafeDelay(_options.InitialDelay.Value, cancellationToken).AnyContext(); if (_options.RunContinuous && _options.InstanceCount > 1) { - var tasks = new List(_options.InstanceCount); - for (int i = 0; i < _options.InstanceCount; i++) + try { - tasks.Add(Task.Run(async () => + var tasks = new List(_options.InstanceCount); + for (int i = 0; i < _options.InstanceCount; i++) { - try + tasks.Add(Task.Run(async () => { - var jobInstance = _options.JobFactory(_serviceProvider); - await jobInstance.RunContinuousAsync(_options.Interval, _options.IterationLimit, cancellationToken).AnyContext(); - } - catch (TaskCanceledException) - { - } - catch (Exception ex) - { - if (_logger.IsEnabled(LogLevel.Error)) - _logger.LogError(ex, "Error running job instance: {Message}", ex.Message); - throw; - } - }, cancellationToken)); + try + { + var jobInstance = _options.JobFactory(_serviceProvider); + await jobInstance.RunContinuousAsync(_options.Interval, _options.IterationLimit, + cancellationToken).AnyContext(); + } + catch (TaskCanceledException) + { + } + catch (Exception ex) + { + if (_logger.IsEnabled(LogLevel.Error)) + _logger.LogError(ex, "Error running job instance: {Message}", ex.Message); + throw; + } + }, cancellationToken)); + } + + await Task.WhenAll(tasks).AnyContext(); + } + catch (OperationCanceledException) + { } - - await Task.WhenAll(tasks).AnyContext(); } else if (_options.RunContinuous && _options.InstanceCount == 1) { diff --git a/src/Foundatio/Jobs/QueueJobBase.cs b/src/Foundatio/Jobs/QueueJobBase.cs index 60d23f35f..e19d50c8a 100644 --- a/src/Foundatio/Jobs/QueueJobBase.cs +++ b/src/Foundatio/Jobs/QueueJobBase.cs @@ -10,25 +10,28 @@ namespace Foundatio.Jobs; -public abstract class QueueJobBase : IQueueJob, IHaveLogger where T : class +public abstract class QueueJobBase : IQueueJob, IHaveLogger, IHaveTimeProvider where T : class { protected readonly ILogger _logger; protected readonly Lazy> _queue; + protected readonly TimeProvider _timeProvider; protected readonly string _queueEntryName = typeof(T).Name; - public QueueJobBase(Lazy> queue, ILoggerFactory loggerFactory = null) + public QueueJobBase(Lazy> queue, TimeProvider timeProvider = null, ILoggerFactory loggerFactory = null) { _queue = queue; + _timeProvider = timeProvider ?? TimeProvider.System; _logger = loggerFactory?.CreateLogger(GetType()) ?? NullLogger.Instance; AutoComplete = true; } - public QueueJobBase(IQueue queue, ILoggerFactory loggerFactory = null) : this(new Lazy>(() => queue), loggerFactory) { } + public QueueJobBase(IQueue queue, TimeProvider timeProvider = null, ILoggerFactory loggerFactory = null) : this(new Lazy>(() => queue), timeProvider, loggerFactory) { } protected bool AutoComplete { get; set; } public string JobId { get; } = Guid.NewGuid().ToString("N").Substring(0, 10); IQueue IQueueJob.Queue => _queue.Value; ILogger IHaveLogger.Logger => _logger; + TimeProvider IHaveTimeProvider.TimeProvider => _timeProvider; public virtual async Task RunAsync(CancellationToken cancellationToken = default) { diff --git a/src/Foundatio/Lock/CacheLockProvider.cs b/src/Foundatio/Lock/CacheLockProvider.cs index e8476c037..3bfb7ac38 100644 --- a/src/Foundatio/Lock/CacheLockProvider.cs +++ b/src/Foundatio/Lock/CacheLockProvider.cs @@ -13,10 +13,11 @@ namespace Foundatio.Lock; -public class CacheLockProvider : ILockProvider, IHaveLogger +public class CacheLockProvider : ILockProvider, IHaveLogger, IHaveTimeProvider { private readonly ICacheClient _cacheClient; private readonly IMessageBus _messageBus; + private readonly TimeProvider _timeProvider; private readonly ConcurrentDictionary _autoResetEvents = new(); private readonly AsyncLock _lock = new(); private bool _isSubscribed; @@ -24,8 +25,11 @@ public class CacheLockProvider : ILockProvider, IHaveLogger private readonly Histogram _lockWaitTimeHistogram; private readonly Counter _lockTimeoutCounter; - public CacheLockProvider(ICacheClient cacheClient, IMessageBus messageBus, ILoggerFactory loggerFactory = null) + public CacheLockProvider(ICacheClient cacheClient, IMessageBus messageBus, ILoggerFactory loggerFactory = null) : this(cacheClient, messageBus, null, loggerFactory) { } + + public CacheLockProvider(ICacheClient cacheClient, IMessageBus messageBus, TimeProvider timeProvider, ILoggerFactory loggerFactory = null) { + _timeProvider = timeProvider ?? cacheClient.GetTimeProvider(); _logger = loggerFactory?.CreateLogger() ?? NullLogger.Instance; _cacheClient = new ScopedCacheClient(cacheClient, "lock"); _messageBus = messageBus; @@ -35,6 +39,7 @@ public CacheLockProvider(ICacheClient cacheClient, IMessageBus messageBus, ILogg } ILogger IHaveLogger.Logger => _logger; + TimeProvider IHaveTimeProvider.TimeProvider => _timeProvider; private async Task EnsureTopicSubscriptionAsync() { @@ -127,8 +132,8 @@ public async Task AcquireAsync(string resource, TimeSpan? timeUntilExpire if (!_isSubscribed) await EnsureTopicSubscriptionAsync().AnyContext(); - var keyExpiration = SystemClock.UtcNow.SafeAdd(await _cacheClient.GetExpirationAsync(resource).AnyContext() ?? TimeSpan.Zero); - var delayAmount = keyExpiration.Subtract(SystemClock.UtcNow); + var keyExpiration = _timeProvider.GetUtcNow().UtcDateTime.SafeAdd(await _cacheClient.GetExpirationAsync(resource).AnyContext() ?? TimeSpan.Zero); + var delayAmount = keyExpiration.Subtract(_timeProvider.GetUtcNow().UtcDateTime); // delay a minimum of 50ms and a maximum of 3 seconds if (delayAmount < TimeSpan.FromMilliseconds(50)) diff --git a/src/Foundatio/Lock/DisposableLock.cs b/src/Foundatio/Lock/DisposableLock.cs index 87df2a384..8706c01e3 100644 --- a/src/Foundatio/Lock/DisposableLock.cs +++ b/src/Foundatio/Lock/DisposableLock.cs @@ -22,7 +22,7 @@ public DisposableLock(string resource, string lockId, TimeSpan timeWaitedForLock Resource = resource; LockId = lockId; TimeWaitedForLock = timeWaitedForLock; - AcquiredTimeUtc = SystemClock.UtcNow; + AcquiredTimeUtc = lockProvider.GetTimeProvider().GetUtcNow().UtcDateTime; _duration = Stopwatch.StartNew(); _logger = logger; _lockProvider = lockProvider; diff --git a/src/Foundatio/Lock/DisposableLockCollection.cs b/src/Foundatio/Lock/DisposableLockCollection.cs index fd2118ce8..41cc00c1b 100644 --- a/src/Foundatio/Lock/DisposableLockCollection.cs +++ b/src/Foundatio/Lock/DisposableLockCollection.cs @@ -18,7 +18,7 @@ internal class DisposableLockCollection : ILock private readonly AsyncLock _lock = new(); private readonly Stopwatch _duration; - public DisposableLockCollection(IEnumerable locks, string lockId, TimeSpan timeWaitedForLock, ILogger logger) + public DisposableLockCollection(IEnumerable locks, string lockId, DateTime acquiredTimeUtc, TimeSpan timeWaitedForLock, ILogger logger) { if (locks == null) throw new ArgumentNullException(nameof(locks)); @@ -27,7 +27,7 @@ public DisposableLockCollection(IEnumerable locks, string lockId, TimeSpa Resource = String.Join("+", _locks.Select(l => l.Resource)); LockId = lockId; TimeWaitedForLock = timeWaitedForLock; - AcquiredTimeUtc = SystemClock.UtcNow; + AcquiredTimeUtc = acquiredTimeUtc; _duration = Stopwatch.StartNew(); _logger = logger; } diff --git a/src/Foundatio/Lock/ILockProvider.cs b/src/Foundatio/Lock/ILockProvider.cs index e9401409a..31a217a81 100644 --- a/src/Foundatio/Lock/ILockProvider.cs +++ b/src/Foundatio/Lock/ILockProvider.cs @@ -156,7 +156,7 @@ public static async Task AcquireAsync(this ILockProvider provider, IEnume var sw = Stopwatch.StartNew(); - var acquiredLocks = new List<(ILock Lock, DateTime LastRenewed)>(resourceList.Length); + var acquiredLocks = new List<(ILock Lock, DateTimeOffset LastRenewed)>(resourceList.Length); foreach (string resource in resourceList) { var l = await provider.AcquireAsync(resource, timeUntilExpires, releaseOnDispose, cancellationToken).AnyContext(); @@ -168,7 +168,7 @@ public static async Task AcquireAsync(this ILockProvider provider, IEnume // Renew any acquired locks, so they stay alive until we have all locks if (acquiredLocks.Count > 0 && renewTime > TimeSpan.Zero) { - var utcNow = SystemClock.UtcNow; + var utcNow = provider.GetTimeProvider().GetUtcNow(); var locksToRenew = acquiredLocks.Where(al => al.LastRenewed < utcNow.Subtract(renewTime)).ToArray(); if (locksToRenew.Length > 0) { @@ -180,7 +180,7 @@ public static async Task AcquireAsync(this ILockProvider provider, IEnume } } - acquiredLocks.Add((l, SystemClock.UtcNow)); + acquiredLocks.Add((l, provider.GetTimeProvider().GetUtcNow())); } sw.Stop(); @@ -216,7 +216,7 @@ public static async Task AcquireAsync(this ILockProvider provider, IEnume if (isTraceLogLevelEnabled) logger.LogTrace("Acquired {LockCount} locks {Resource} after {Duration:g}", resourceList.Length, resourceList, sw.Elapsed); - return new DisposableLockCollection(locks, String.Join("+", locks.Select(l => l.LockId)), sw.Elapsed, logger); + return new DisposableLockCollection(locks, String.Join("+", locks.Select(l => l.LockId)), provider.GetTimeProvider().GetUtcNow().UtcDateTime, sw.Elapsed, logger); } public static async Task AcquireAsync(this ILockProvider provider, IEnumerable resources, TimeSpan? timeUntilExpires, TimeSpan? acquireTimeout) diff --git a/src/Foundatio/Lock/ThrottlingLockProvider.cs b/src/Foundatio/Lock/ThrottlingLockProvider.cs index c62699c2d..72f064d75 100644 --- a/src/Foundatio/Lock/ThrottlingLockProvider.cs +++ b/src/Foundatio/Lock/ThrottlingLockProvider.cs @@ -9,15 +9,17 @@ namespace Foundatio.Lock; -public class ThrottlingLockProvider : ILockProvider, IHaveLogger +public class ThrottlingLockProvider : ILockProvider, IHaveLogger, IHaveTimeProvider { private readonly ICacheClient _cacheClient; private readonly TimeSpan _throttlingPeriod = TimeSpan.FromMinutes(15); private readonly int _maxHitsPerPeriod; private readonly ILogger _logger; + private readonly TimeProvider _timeProvider; - public ThrottlingLockProvider(ICacheClient cacheClient, int maxHitsPerPeriod = 100, TimeSpan? throttlingPeriod = null, ILoggerFactory loggerFactory = null) + public ThrottlingLockProvider(ICacheClient cacheClient, int maxHitsPerPeriod = 100, TimeSpan? throttlingPeriod = null, TimeProvider timeProvider = null, ILoggerFactory loggerFactory = null) { + _timeProvider = timeProvider ?? cacheClient.GetTimeProvider(); _logger = loggerFactory?.CreateLogger() ?? NullLogger.Instance; _cacheClient = new ScopedCacheClient(cacheClient, "lock:throttled"); _maxHitsPerPeriod = maxHitsPerPeriod; @@ -30,6 +32,7 @@ public ThrottlingLockProvider(ICacheClient cacheClient, int maxHitsPerPeriod = 1 } ILogger IHaveLogger.Logger => _logger; + TimeProvider IHaveTimeProvider.TimeProvider => _timeProvider; public async Task AcquireAsync(string resource, TimeSpan? timeUntilExpires = null, bool releaseOnDispose = true, CancellationToken cancellationToken = default) { @@ -43,19 +46,19 @@ public async Task AcquireAsync(string resource, TimeSpan? timeUntilExpire var sw = Stopwatch.StartNew(); do { - string cacheKey = GetCacheKey(resource, SystemClock.UtcNow); + string cacheKey = GetCacheKey(resource, _timeProvider.GetUtcNow().UtcDateTime); try { if (isTraceLogLevelEnabled) - _logger.LogTrace("Current time: {CurrentTime} throttle: {ThrottlingPeriod} key: {Key}", SystemClock.UtcNow.ToString("mm:ss.fff"), SystemClock.UtcNow.Floor(_throttlingPeriod).ToString("mm:ss.fff"), cacheKey); + _logger.LogTrace("Current time: {CurrentTime} throttle: {ThrottlingPeriod} key: {Key}", _timeProvider.GetUtcNow().ToString("mm:ss.fff"), _timeProvider.GetUtcNow().UtcDateTime.Floor(_throttlingPeriod).ToString("mm:ss.fff"), cacheKey); var hitCount = await _cacheClient.GetAsync(cacheKey, 0).AnyContext(); if (isTraceLogLevelEnabled) _logger.LogTrace("Current hit count: {HitCount} max: {MaxHitsPerPeriod}", hitCount, _maxHitsPerPeriod); if (hitCount <= _maxHitsPerPeriod - 1) { - hitCount = await _cacheClient.IncrementAsync(cacheKey, 1, SystemClock.UtcNow.Ceiling(_throttlingPeriod)).AnyContext(); + hitCount = await _cacheClient.IncrementAsync(cacheKey, 1, _timeProvider.GetUtcNow().UtcDateTime.Ceiling(_throttlingPeriod)).AnyContext(); // make sure someone didn't beat us to it. if (hitCount <= _maxHitsPerPeriod) @@ -74,16 +77,16 @@ public async Task AcquireAsync(string resource, TimeSpan? timeUntilExpire if (cancellationToken.IsCancellationRequested) break; - var sleepUntil = SystemClock.UtcNow.Ceiling(_throttlingPeriod).AddMilliseconds(1); - if (sleepUntil > SystemClock.UtcNow) + var sleepUntil = _timeProvider.GetUtcNow().UtcDateTime.Ceiling(_throttlingPeriod).AddMilliseconds(1); + if (sleepUntil > _timeProvider.GetUtcNow()) { - if (isTraceLogLevelEnabled) _logger.LogTrace("Sleeping until key expires: {SleepUntil}", sleepUntil - SystemClock.UtcNow); - await SystemClock.SleepAsync(sleepUntil - SystemClock.UtcNow, cancellationToken).AnyContext(); + if (isTraceLogLevelEnabled) _logger.LogTrace("Sleeping until key expires: {SleepUntil}", sleepUntil - _timeProvider.GetUtcNow()); + await _timeProvider.SafeDelay(sleepUntil - _timeProvider.GetUtcNow(), cancellationToken).AnyContext(); } else { if (isTraceLogLevelEnabled) _logger.LogTrace("Default sleep"); - await SystemClock.SleepAsync(50, cancellationToken).AnyContext(); + await _timeProvider.SafeDelay(TimeSpan.FromMilliseconds(50), cancellationToken).AnyContext(); } } catch (OperationCanceledException) @@ -97,7 +100,7 @@ public async Task AcquireAsync(string resource, TimeSpan? timeUntilExpire if (errors >= 3) break; - await SystemClock.SleepSafeAsync(50, cancellationToken).AnyContext(); + await _timeProvider.SafeDelay(TimeSpan.FromMilliseconds(50), cancellationToken).AnyContext(); } } while (!cancellationToken.IsCancellationRequested); @@ -116,7 +119,7 @@ public async Task AcquireAsync(string resource, TimeSpan? timeUntilExpire public async Task IsLockedAsync(string resource) { - string cacheKey = GetCacheKey(resource, SystemClock.UtcNow); + string cacheKey = GetCacheKey(resource, _timeProvider.GetUtcNow().UtcDateTime); long hitCount = await _cacheClient.GetAsync(cacheKey, 0).AnyContext(); return hitCount >= _maxHitsPerPeriod; } diff --git a/src/Foundatio/Messaging/MessageBusBase.cs b/src/Foundatio/Messaging/MessageBusBase.cs index a2d303aab..899e4348c 100644 --- a/src/Foundatio/Messaging/MessageBusBase.cs +++ b/src/Foundatio/Messaging/MessageBusBase.cs @@ -19,6 +19,7 @@ public abstract class MessageBusBase : IMessageBus, IDisposable where protected readonly ConcurrentDictionary _subscribers = new(); protected readonly TOptions _options; protected readonly ILogger _logger; + protected readonly TimeProvider _timeProvider; protected readonly ISerializer _serializer; private bool _isDisposed; @@ -27,6 +28,7 @@ public MessageBusBase(TOptions options) _options = options ?? throw new ArgumentNullException(nameof(options)); var loggerFactory = options?.LoggerFactory ?? NullLoggerFactory.Instance; _logger = loggerFactory.CreateLogger(GetType()); + _timeProvider = options.TimeProvider; _serializer = options.Serializer ?? DefaultSerializer.Instance; MessageBusId = _options.Topic + Guid.NewGuid().ToString("N").Substring(10); _messageBusDisposedCancellationTokenSource = new CancellationTokenSource(); @@ -347,10 +349,10 @@ protected void SendDelayedMessage(Type messageType, object message, TimeSpan del if (delay <= TimeSpan.Zero) throw new ArgumentOutOfRangeException(nameof(delay)); - var sendTime = SystemClock.UtcNow.SafeAdd(delay); + var sendTime = _timeProvider.GetUtcNow().UtcDateTime.SafeAdd(delay); Task.Factory.StartNew(async () => { - await SystemClock.SleepSafeAsync(delay, _messageBusDisposedCancellationTokenSource.Token).AnyContext(); + await _timeProvider.SafeDelay(delay, _messageBusDisposedCancellationTokenSource.Token).AnyContext(); bool isTraceLevelEnabled = _logger.IsEnabled(LogLevel.Trace); if (_messageBusDisposedCancellationTokenSource.IsCancellationRequested) diff --git a/src/Foundatio/Metrics/BufferedMetricsClientBase.cs b/src/Foundatio/Metrics/BufferedMetricsClientBase.cs deleted file mode 100644 index f2c7665b1..000000000 --- a/src/Foundatio/Metrics/BufferedMetricsClientBase.cs +++ /dev/null @@ -1,363 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Foundatio.AsyncEx; -using Foundatio.Utility; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; - -namespace Foundatio.Metrics; - -public abstract class BufferedMetricsClientBase : IBufferedMetricsClient -{ - protected readonly List _timeBuckets = new() { - new TimeBucket { Size = TimeSpan.FromMinutes(1) } - }; - - private readonly ConcurrentQueue _queue = new(); - private readonly Timer _flushTimer; - private readonly SharedMetricsClientOptions _options; - protected readonly ILogger _logger; - - public BufferedMetricsClientBase(SharedMetricsClientOptions options) - { - _options = options; - _logger = options.LoggerFactory?.CreateLogger(GetType()) ?? NullLogger.Instance; - if (options.Buffered) - _flushTimer = new Timer(OnMetricsTimer, null, TimeSpan.FromSeconds(1), TimeSpan.FromSeconds(2)); - } - - public AsyncEvent Counted { get; } = new AsyncEvent(true); - - protected virtual Task OnCountedAsync(long value) - { - var counted = Counted; - if (counted == null) - return Task.CompletedTask; - - var args = new CountedEventArgs { Value = value }; - return counted.InvokeAsync(this, args); - } - - public void Counter(string name, int value = 1) - { - var entry = new MetricEntry { Name = name, Type = MetricType.Counter, Counter = value }; - if (!_options.Buffered) - SubmitMetric(entry); - else - _queue.Enqueue(entry); - } - - public void Gauge(string name, double value) - { - var entry = new MetricEntry { Name = name, Type = MetricType.Gauge, Gauge = value }; - if (!_options.Buffered) - SubmitMetric(entry); - else - _queue.Enqueue(entry); - } - - public void Timer(string name, int milliseconds) - { - var entry = new MetricEntry { Name = name, Type = MetricType.Timing, Timing = milliseconds }; - if (!_options.Buffered) - SubmitMetric(entry); - else - _queue.Enqueue(entry); - } - - private void OnMetricsTimer(object state) - { - if (_sendingMetrics || _queue.IsEmpty) - return; - - try - { - FlushAsync().AnyContext().GetAwaiter().GetResult(); - } - catch (Exception ex) - { - if (_logger.IsEnabled(LogLevel.Error)) - _logger.LogError(ex, "Error flushing metrics: {Message}", ex.Message); - } - } - - private bool _sendingMetrics = false; - public async Task FlushAsync() - { - if (_sendingMetrics || _queue.IsEmpty) - return; - - bool isTraceLogLevelEnabled = _logger.IsEnabled(LogLevel.Trace); - if (isTraceLogLevelEnabled) _logger.LogTrace("Flushing metrics: count={Count}", _queue.Count); - - try - { - _sendingMetrics = true; - - var startTime = SystemClock.UtcNow; - var entries = new List(); - while (_queue.TryDequeue(out var entry)) - { - entries.Add(entry); - if (entry.EnqueuedDate > startTime) - break; - } - - if (entries.Count == 0) - return; - - if (isTraceLogLevelEnabled) _logger.LogTrace("Dequeued {Count} metrics", entries.Count); - await SubmitMetricsAsync(entries).AnyContext(); - } - finally - { - _sendingMetrics = false; - } - } - - private void SubmitMetric(MetricEntry metric) - { - SubmitMetricsAsync(new List { metric }).AnyContext().GetAwaiter().GetResult(); - } - - protected virtual async Task SubmitMetricsAsync(List metrics) - { - bool isTraceLogLevelEnabled = _logger.IsEnabled(LogLevel.Trace); - foreach (var timeBucket in _timeBuckets) - { - try - { - // counters - var counters = metrics.Where(e => e.Type == MetricType.Counter).ToList(); - var groupedCounters = counters - .GroupBy(e => new MetricKey(e.EnqueuedDate.Floor(timeBucket.Size), timeBucket.Size, e.Name)) - .Select(e => new AggregatedCounterMetric - { - Key = e.Key, - Value = e.Sum(c => c.Counter), - Entries = e.ToList() - }).ToList(); - - if (metrics.Count > 1 && counters.Count > 0 && isTraceLogLevelEnabled) - _logger.LogTrace("Aggregated {CountersCount} counter(s) into {GroupedCountersCount} counter group(s)", counters.Count, groupedCounters.Count); - - // gauges - var gauges = metrics.Where(e => e.Type == MetricType.Gauge).ToList(); - var groupedGauges = gauges - .GroupBy(e => new MetricKey(e.EnqueuedDate.Floor(timeBucket.Size), timeBucket.Size, e.Name)) - .Select(e => new AggregatedGaugeMetric - { - Key = e.Key, - Count = e.Count(), - Total = e.Sum(c => c.Gauge), - Last = e.Last().Gauge, - Min = e.Min(c => c.Gauge), - Max = e.Max(c => c.Gauge), - Entries = e.ToList() - }).ToList(); - - if (metrics.Count > 1 && gauges.Count > 0 && isTraceLogLevelEnabled) - _logger.LogTrace("Aggregated {GaugesCount} gauge(s) into {GroupedGaugesCount} gauge group(s)", gauges.Count, groupedGauges.Count); - - // timings - var timings = metrics.Where(e => e.Type == MetricType.Timing).ToList(); - var groupedTimings = timings - .GroupBy(e => new MetricKey(e.EnqueuedDate.Floor(timeBucket.Size), timeBucket.Size, e.Name)) - .Select(e => new AggregatedTimingMetric - { - Key = e.Key, - Count = e.Count(), - TotalDuration = e.Sum(c => (long)c.Timing), - MinDuration = e.Min(c => c.Timing), - MaxDuration = e.Max(c => c.Timing), - Entries = e.ToList() - }).ToList(); - - if (metrics.Count > 1 && timings.Count > 0 && isTraceLogLevelEnabled) - _logger.LogTrace("Aggregated {TimingsCount} timing(s) into {GroupedTimingsCount} timing group(s)", timings.Count, groupedTimings.Count); - - // store aggregated metrics - if (counters.Count > 0 || gauges.Count > 0 || timings.Count > 0) - await StoreAggregatedMetricsInternalAsync(timeBucket, groupedCounters, groupedGauges, groupedTimings).AnyContext(); - } - catch (Exception ex) - { - if (_logger.IsEnabled(LogLevel.Error)) - _logger.LogError(ex, "Error aggregating metrics: {Message}", ex.Message); - throw; - } - } - } - - private async Task StoreAggregatedMetricsInternalAsync(TimeBucket timeBucket, ICollection counters, ICollection gauges, ICollection timings) - { - bool isTraceLogLevelEnabled = _logger.IsEnabled(LogLevel.Trace); - if (isTraceLogLevelEnabled) - _logger.LogTrace("Storing {CountersCount} counters, {GaugesCount} gauges, {TimingsCount} timings.", counters.Count, gauges.Count, timings.Count); - - try - { - await Run.WithRetriesAsync(() => StoreAggregatedMetricsAsync(timeBucket, counters, gauges, timings)).AnyContext(); - } - catch (Exception ex) - { - if (_logger.IsEnabled(LogLevel.Error)) - _logger.LogError(ex, "Error storing aggregated metrics: {Message}", ex.Message); - throw; - } - - await OnCountedAsync(counters.Sum(c => c.Value)).AnyContext(); - if (isTraceLogLevelEnabled) _logger.LogTrace("Done storing aggregated metrics"); - } - - protected abstract Task StoreAggregatedMetricsAsync(TimeBucket timeBucket, ICollection counters, ICollection gauges, ICollection timings); - - public async Task WaitForCounterAsync(string statName, long count = 1, TimeSpan? timeout = null) - { - using var cancellationTokenSource = timeout.ToCancellationTokenSource(TimeSpan.FromSeconds(10)); - return await WaitForCounterAsync(statName, () => Task.CompletedTask, count, cancellationTokenSource.Token).AnyContext(); - } - - public async Task WaitForCounterAsync(string statName, Func work, long count = 1, CancellationToken cancellationToken = default) - { - if (count <= 0) - return true; - - long currentCount = count; - var resetEvent = new AsyncAutoResetEvent(false); - var start = SystemClock.UtcNow; - - bool isTraceLogLevelEnabled = _logger.IsEnabled(LogLevel.Trace); - using (Counted.AddHandler((s, e) => - { - currentCount -= e.Value; - resetEvent.Set(); - return Task.CompletedTask; - })) - { - if (isTraceLogLevelEnabled) _logger.LogTrace("Wait: count={Count}", currentCount); - - if (work != null) - await work().AnyContext(); - - if (currentCount <= 0) - return true; - - do - { - try - { - await resetEvent.WaitAsync(cancellationToken).AnyContext(); - } - catch (OperationCanceledException) { } - - if (isTraceLogLevelEnabled) - _logger.LogTrace("Got signal: count={CurrentCount} expected={Count}", currentCount, count); - } while (cancellationToken.IsCancellationRequested == false && currentCount > 0); - } - - if (isTraceLogLevelEnabled) - _logger.LogTrace("Done waiting: count={CurrentCount} expected={Count} success={Success} time={Time}", currentCount, count, currentCount <= 0, SystemClock.UtcNow.Subtract(start)); - - return currentCount <= 0; - } - - public virtual void Dispose() - { - _flushTimer?.Dispose(); - FlushAsync().AnyContext().GetAwaiter().GetResult(); - _queue?.Clear(); - } - - [DebuggerDisplay("Date: {EnqueuedDate} Type: {Type} Name: {Name} Counter: {Counter} Gauge: {Gauge} Timing: {Timing}")] - protected class MetricEntry - { - public DateTime EnqueuedDate { get; } = SystemClock.UtcNow; - public string Name { get; set; } - public MetricType Type { get; set; } - public int Counter { get; set; } - public double Gauge { get; set; } - public int Timing { get; set; } - } - - protected enum MetricType - { - Counter, - Gauge, - Timing - } - - [DebuggerDisplay("Time: {Time} Key: {Key}")] - protected class MetricBucket - { - public string Key { get; set; } - public DateTime Time { get; set; } - } - - protected interface IAggregatedMetric where T : class - { - MetricKey Key { get; set; } - ICollection Entries { get; set; } - T Add(T other); - } - - protected class AggregatedCounterMetric : IAggregatedMetric - { - public MetricKey Key { get; set; } - public long Value { get; set; } - public ICollection Entries { get; set; } - - public AggregatedCounterMetric Add(AggregatedCounterMetric other) - { - return this; - } - } - - protected class AggregatedGaugeMetric : IAggregatedMetric - { - public MetricKey Key { get; set; } - public int Count { get; set; } - public double Total { get; set; } - public double Last { get; set; } - public double Min { get; set; } - public double Max { get; set; } - public ICollection Entries { get; set; } - - public AggregatedGaugeMetric Add(AggregatedGaugeMetric other) - { - return this; - } - } - - protected class AggregatedTimingMetric : IAggregatedMetric - { - public MetricKey Key { get; set; } - public int Count { get; set; } - public long TotalDuration { get; set; } - public int MinDuration { get; set; } - public int MaxDuration { get; set; } - public ICollection Entries { get; set; } - - public AggregatedTimingMetric Add(AggregatedTimingMetric other) - { - return this; - } - } - - [DebuggerDisplay("Size: {Size} Ttl: {Ttl}")] - protected struct TimeBucket - { - public TimeSpan Size { get; set; } - public TimeSpan Ttl { get; set; } - } -} - -public class CountedEventArgs : EventArgs -{ - public long Value { get; set; } -} diff --git a/src/Foundatio/Metrics/CacheBucketMetricsClientBase.cs b/src/Foundatio/Metrics/CacheBucketMetricsClientBase.cs deleted file mode 100644 index 366e14f7d..000000000 --- a/src/Foundatio/Metrics/CacheBucketMetricsClientBase.cs +++ /dev/null @@ -1,282 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using Foundatio.Caching; -using Foundatio.Utility; -using Microsoft.Extensions.Logging; - -namespace Foundatio.Metrics; - -public abstract class CacheBucketMetricsClientBase : BufferedMetricsClientBase, IMetricsClientStats -{ - protected readonly ICacheClient _cache; - private readonly string _prefix; - - public CacheBucketMetricsClientBase(ICacheClient cache, SharedMetricsClientOptions options) : base(options) - { - _cache = cache; - _prefix = !String.IsNullOrEmpty(options.Prefix) ? (!options.Prefix.EndsWith(":") ? options.Prefix + ":" : options.Prefix) : String.Empty; - - _timeBuckets.Clear(); - _timeBuckets.Add(new TimeBucket { Size = TimeSpan.FromMinutes(5), Ttl = TimeSpan.FromHours(1) }); - _timeBuckets.Add(new TimeBucket { Size = TimeSpan.FromHours(1), Ttl = TimeSpan.FromDays(7) }); - } - - protected override Task StoreAggregatedMetricsAsync(TimeBucket timeBucket, ICollection counters, ICollection gauges, ICollection timings) - { - var tasks = new List(); - foreach (var counter in counters) - tasks.Add(StoreCounterAsync(timeBucket, counter)); - - foreach (var gauge in gauges) - tasks.Add(StoreGaugeAsync(timeBucket, gauge)); - - foreach (var timing in timings) - tasks.Add(StoreTimingAsync(timeBucket, timing)); - - return Task.WhenAll(tasks); - } - - private async Task StoreCounterAsync(TimeBucket timeBucket, AggregatedCounterMetric counter) - { - if (_logger.IsEnabled(LogLevel.Trace)) - _logger.LogTrace("Storing counter name={Name} value={Value} time={Duration}", counter.Key.Name, counter.Value, counter.Key.Duration); - - string bucketKey = GetBucketKey(CacheMetricNames.Counter, counter.Key.Name, counter.Key.StartTimeUtc, timeBucket.Size); - await _cache.IncrementAsync(bucketKey, counter.Value, timeBucket.Ttl).AnyContext(); - - if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("Done storing counter name={Name}", counter.Key.Name); - } - - private async Task StoreGaugeAsync(TimeBucket timeBucket, AggregatedGaugeMetric gauge) - { - if (_logger.IsEnabled(LogLevel.Trace)) - _logger.LogTrace("Storing gauge name={Name} count={Count} total={Total} last={Last} min={Min} max={Max} time={StartTimeUtc}", gauge.Key.Name, gauge.Count, gauge.Total, gauge.Last, gauge.Min, gauge.Max, gauge.Key.StartTimeUtc); - - string countKey = GetBucketKey(CacheMetricNames.Gauge, gauge.Key.Name, gauge.Key.StartTimeUtc, timeBucket.Size, CacheMetricNames.Count); - string totalDurationKey = GetBucketKey(CacheMetricNames.Gauge, gauge.Key.Name, gauge.Key.StartTimeUtc, timeBucket.Size, CacheMetricNames.Total); - string lastKey = GetBucketKey(CacheMetricNames.Gauge, gauge.Key.Name, gauge.Key.StartTimeUtc, timeBucket.Size, CacheMetricNames.Last); - string minKey = GetBucketKey(CacheMetricNames.Gauge, gauge.Key.Name, gauge.Key.StartTimeUtc, timeBucket.Size, CacheMetricNames.Min); - string maxKey = GetBucketKey(CacheMetricNames.Gauge, gauge.Key.Name, gauge.Key.StartTimeUtc, timeBucket.Size, CacheMetricNames.Max); - - await Task.WhenAll( - _cache.IncrementAsync(countKey, gauge.Count, timeBucket.Ttl), - _cache.IncrementAsync(totalDurationKey, gauge.Total, timeBucket.Ttl), - _cache.SetAsync(lastKey, gauge.Last, timeBucket.Ttl), - _cache.SetIfLowerAsync(minKey, gauge.Min, timeBucket.Ttl), - _cache.SetIfHigherAsync(maxKey, gauge.Max, timeBucket.Ttl) - ).AnyContext(); - - if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("Done storing gauge name={Name}", gauge.Key.Name); - } - - private async Task StoreTimingAsync(TimeBucket timeBucket, AggregatedTimingMetric timing) - { - if (_logger.IsEnabled(LogLevel.Trace)) - _logger.LogTrace("Storing timing name={Name} count={Count} total={TotalDuration} min={MinDuration} max={MaxDuration} time={StartTimeUtc}", timing.Key.Name, timing.Count, timing.TotalDuration, timing.MinDuration, timing.MaxDuration, timing.Key.StartTimeUtc); - - string countKey = GetBucketKey(CacheMetricNames.Timing, timing.Key.Name, timing.Key.StartTimeUtc, timeBucket.Size, CacheMetricNames.Count); - string totalDurationKey = GetBucketKey(CacheMetricNames.Timing, timing.Key.Name, timing.Key.StartTimeUtc, timeBucket.Size, CacheMetricNames.Total); - string maxKey = GetBucketKey(CacheMetricNames.Timing, timing.Key.Name, timing.Key.StartTimeUtc, timeBucket.Size, CacheMetricNames.Max); - string minKey = GetBucketKey(CacheMetricNames.Timing, timing.Key.Name, timing.Key.StartTimeUtc, timeBucket.Size, CacheMetricNames.Min); - - await Task.WhenAll( - _cache.IncrementAsync(countKey, timing.Count, timeBucket.Ttl), - _cache.IncrementAsync(totalDurationKey, timing.TotalDuration, timeBucket.Ttl), - _cache.SetIfHigherAsync(maxKey, timing.MaxDuration, timeBucket.Ttl), - _cache.SetIfLowerAsync(minKey, timing.MinDuration, timeBucket.Ttl) - ).AnyContext(); - - if (_logger.IsEnabled(LogLevel.Trace)) _logger.LogTrace("Done storing timing name={Name}", timing.Key.Name); - } - - public async Task GetCounterStatsAsync(string name, DateTime? start = null, DateTime? end = null, int dataPoints = 20) - { - if (!start.HasValue) - start = SystemClock.UtcNow.AddHours(-4); - - if (!end.HasValue) - end = SystemClock.UtcNow; - - var interval = end.Value.Subtract(start.Value).TotalMinutes > 180 ? TimeSpan.FromHours(1) : TimeSpan.FromMinutes(5); - - var countBuckets = GetMetricBuckets(CacheMetricNames.Counter, name, start.Value, end.Value, interval); - var countResults = await _cache.GetAllAsync(countBuckets.Select(k => k.Key)).AnyContext(); - - ICollection stats = new List(); - foreach (var bucket in countBuckets) - { - string countKey = bucket.Key; - - stats.Add(new CounterStat - { - Time = bucket.Time, - Count = countResults[countKey].Value - }); - } - - stats = stats.ReduceTimeSeries(s => s.Time, (s, d) => new CounterStat - { - Time = d, - Count = s.Sum(i => i.Count) - }, dataPoints); - - return new CounterStatSummary(name, stats, start.Value, end.Value); - } - - public async Task GetGaugeStatsAsync(string name, DateTime? start = null, DateTime? end = null, int dataPoints = 20) - { - if (!start.HasValue) - start = SystemClock.UtcNow.AddHours(-4); - - if (!end.HasValue) - end = SystemClock.UtcNow; - - var interval = end.Value.Subtract(start.Value).TotalMinutes > 180 ? TimeSpan.FromHours(1) : TimeSpan.FromMinutes(5); - - var countBuckets = GetMetricBuckets(CacheMetricNames.Gauge, name, start.Value, end.Value, interval, CacheMetricNames.Count); - var totalBuckets = GetMetricBuckets(CacheMetricNames.Gauge, name, start.Value, end.Value, interval, CacheMetricNames.Total); - var lastBuckets = GetMetricBuckets(CacheMetricNames.Gauge, name, start.Value, end.Value, interval, CacheMetricNames.Last); - var minBuckets = GetMetricBuckets(CacheMetricNames.Gauge, name, start.Value, end.Value, interval, CacheMetricNames.Min); - var maxBuckets = GetMetricBuckets(CacheMetricNames.Gauge, name, start.Value, end.Value, interval, CacheMetricNames.Max); - - var countTask = _cache.GetAllAsync(countBuckets.Select(k => k.Key)); - var totalTask = _cache.GetAllAsync(totalBuckets.Select(k => k.Key)); - var lastTask = _cache.GetAllAsync(lastBuckets.Select(k => k.Key)); - var minTask = _cache.GetAllAsync(minBuckets.Select(k => k.Key)); - var maxTask = _cache.GetAllAsync(maxBuckets.Select(k => k.Key)); - - await Task.WhenAll(countTask, totalTask, lastTask, minTask, maxTask).AnyContext(); - - ICollection stats = new List(); - for (int i = 0; i < maxBuckets.Count; i++) - { - string countKey = countBuckets[i].Key; - string totalKey = totalBuckets[i].Key; - string minKey = minBuckets[i].Key; - string maxKey = maxBuckets[i].Key; - string lastKey = lastBuckets[i].Key; - - stats.Add(new GaugeStat - { - Time = maxBuckets[i].Time, - Count = countTask.Result[countKey].Value, - Total = totalTask.Result[totalKey].Value, - Min = minTask.Result[minKey].Value, - Max = maxTask.Result[maxKey].Value, - Last = lastTask.Result[lastKey].Value - }); - } - - stats = stats.ReduceTimeSeries(s => s.Time, (s, d) => new GaugeStat - { - Time = d, - Count = s.Sum(i => i.Count), - Total = s.Sum(i => i.Total), - Min = s.Min(i => i.Min), - Max = s.Max(i => i.Max), - Last = s.Last().Last - }, dataPoints); - - return new GaugeStatSummary(name, stats, start.Value, end.Value); - } - - public async Task GetTimerStatsAsync(string name, DateTime? start = null, DateTime? end = null, int dataPoints = 20) - { - if (!start.HasValue) - start = SystemClock.UtcNow.AddHours(-4); - - if (!end.HasValue) - end = SystemClock.UtcNow; - - var interval = end.Value.Subtract(start.Value).TotalMinutes > 180 ? TimeSpan.FromHours(1) : TimeSpan.FromMinutes(5); - - var countBuckets = GetMetricBuckets(CacheMetricNames.Timing, name, start.Value, end.Value, interval, CacheMetricNames.Count); - var durationBuckets = GetMetricBuckets(CacheMetricNames.Timing, name, start.Value, end.Value, interval, CacheMetricNames.Total); - var minBuckets = GetMetricBuckets(CacheMetricNames.Timing, name, start.Value, end.Value, interval, CacheMetricNames.Min); - var maxBuckets = GetMetricBuckets(CacheMetricNames.Timing, name, start.Value, end.Value, interval, CacheMetricNames.Max); - - var countTask = _cache.GetAllAsync(countBuckets.Select(k => k.Key)); - var durationTask = _cache.GetAllAsync(durationBuckets.Select(k => k.Key)); - var minTask = _cache.GetAllAsync(minBuckets.Select(k => k.Key)); - var maxTask = _cache.GetAllAsync(maxBuckets.Select(k => k.Key)); - - await Task.WhenAll(countTask, durationTask, minTask, maxTask).AnyContext(); - - ICollection stats = new List(); - for (int i = 0; i < countBuckets.Count; i++) - { - string countKey = countBuckets[i].Key; - string durationKey = durationBuckets[i].Key; - string minKey = minBuckets[i].Key; - string maxKey = maxBuckets[i].Key; - - stats.Add(new TimingStat - { - Time = countBuckets[i].Time, - Count = countTask.Result[countKey].Value, - TotalDuration = durationTask.Result[durationKey].Value, - MinDuration = minTask.Result[minKey].Value, - MaxDuration = maxTask.Result[maxKey].Value - }); - } - - stats = stats.ReduceTimeSeries(s => s.Time, (s, d) => new TimingStat - { - Time = d, - Count = s.Sum(i => i.Count), - MinDuration = s.Min(i => i.MinDuration), - MaxDuration = s.Max(i => i.MaxDuration), - TotalDuration = s.Sum(i => i.TotalDuration) - }, dataPoints); - - return new TimingStatSummary(name, stats, start.Value, end.Value); - } - - private string GetBucketKey(string metricType, string statName, DateTime? dateTime = null, TimeSpan? interval = null, string suffix = null) - { - if (interval == null) - interval = _timeBuckets[0].Size; - - if (dateTime == null) - dateTime = SystemClock.UtcNow; - - dateTime = dateTime.Value.Floor(interval.Value); - - suffix = !String.IsNullOrEmpty(suffix) ? ":" + suffix : String.Empty; - return String.Concat(_prefix, "m:", metricType, ":", statName, ":", interval.Value.TotalMinutes, ":", dateTime.Value.ToString("yy-MM-dd-hh-mm"), suffix); - } - - private List GetMetricBuckets(string metricType, string statName, DateTime start, DateTime end, TimeSpan? interval = null, string suffix = null) - { - if (interval == null) - interval = _timeBuckets[0].Size; - - start = start.Floor(interval.Value); - end = end.Floor(interval.Value); - - var current = start; - var keys = new List(); - while (current <= end) - { - keys.Add(new MetricBucket { Key = GetBucketKey(metricType, statName, current, interval, suffix), Time = current }); - current = current.Add(interval.Value); - } - - return keys; - } - - private class CacheMetricNames - { - public const string Counter = "c"; - public const string Gauge = "g"; - public const string Timing = "t"; - - public const string Count = "cnt"; - public const string Total = "tot"; - public const string Max = "max"; - public const string Min = "min"; - public const string Last = "last"; - } -} diff --git a/src/Foundatio/Metrics/CounterStat.cs b/src/Foundatio/Metrics/CounterStat.cs deleted file mode 100644 index d71ad0650..000000000 --- a/src/Foundatio/Metrics/CounterStat.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using Foundatio.Utility; - -namespace Foundatio.Metrics; - -[DebuggerDisplay("Time: {Time} Count: {Count}")] -public class CounterStat -{ - public DateTime Time { get; set; } - public long Count { get; set; } -} - -[DebuggerDisplay("Time: {StartTime}-{EndTime} Count: {Count}")] -public class CounterStatSummary -{ - public CounterStatSummary(string name, ICollection stats, DateTime start, DateTime end) - { - Name = name; - Stats.AddRange(stats); - Count = stats.Count > 0 ? Stats.Sum(s => s.Count) : 0; - StartTime = start; - EndTime = end; - } - - public string Name { get; private set; } - public DateTime StartTime { get; private set; } - public DateTime EndTime { get; private set; } - public ICollection Stats { get; } = new List(); - public long Count { get; private set; } - - public override string ToString() - { - return $"Counter: {Name} Value: {Count}"; - } -} diff --git a/src/Foundatio/Metrics/DiagnosticsMetricsClient.cs b/src/Foundatio/Metrics/DiagnosticsMetricsClient.cs deleted file mode 100644 index 2ec18f7fa..000000000 --- a/src/Foundatio/Metrics/DiagnosticsMetricsClient.cs +++ /dev/null @@ -1,63 +0,0 @@ -#pragma warning disable 612, 618 - -using System; -using System.Collections.Concurrent; -using System.Diagnostics.Metrics; - -namespace Foundatio.Metrics; - -public class DiagnosticsMetricsClient : IMetricsClient -{ - private readonly ConcurrentDictionary> _counters = new(); - private readonly ConcurrentDictionary _gauges = new(); - private readonly ConcurrentDictionary> _timers = new(); - private readonly Meter _meter; - private readonly string _prefix; - - public DiagnosticsMetricsClient() : this(o => o) { } - - public DiagnosticsMetricsClient(DiagnosticsMetricsClientOptions options) - { - _prefix = !String.IsNullOrEmpty(options.Prefix) ? (!options.Prefix.EndsWith(".") ? options.Prefix + "." : options.Prefix) : String.Empty; - _meter = new Meter(options.MeterName ?? "Foundatio.MetricsClient", options.MeterVersion ?? FoundatioDiagnostics.AssemblyName.Version.ToString()); - } - - public DiagnosticsMetricsClient(Builder config) - : this(config(new DiagnosticsMetricsClientOptionsBuilder()).Build()) { } - - public void Counter(string name, int value = 1) - { - var counter = _counters.GetOrAdd(_prefix + name, _meter.CreateCounter(name)); - counter.Add(value); - } - - public void Gauge(string name, double value) - { - var gauge = _gauges.GetOrAdd(_prefix + name, new GaugeInfo(_meter, name)); - gauge.Value = value; - } - - public void Timer(string name, int milliseconds) - { - var timer = _timers.GetOrAdd(_prefix + name, _meter.CreateHistogram(name, "ms")); - timer.Record(milliseconds); - } - - public void Dispose() - { - _meter.Dispose(); - } - - private class GaugeInfo - { - public GaugeInfo(Meter meter, string name) - { - Gauge = meter.CreateObservableGauge(name, () => Value); - } - - public ObservableGauge Gauge { get; } - public double Value { get; set; } - } -} - -#pragma warning restore 612, 618 diff --git a/src/Foundatio/Metrics/DiagnosticsMetricsClientOptions.cs b/src/Foundatio/Metrics/DiagnosticsMetricsClientOptions.cs deleted file mode 100644 index 3e02d7b8f..000000000 --- a/src/Foundatio/Metrics/DiagnosticsMetricsClientOptions.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; - -namespace Foundatio.Metrics; - -public class DiagnosticsMetricsClientOptions : SharedMetricsClientOptions -{ - public string MeterName { get; set; } - public string MeterVersion { get; set; } -} - -public class DiagnosticsMetricsClientOptionsBuilder : SharedMetricsClientOptionsBuilder -{ - public DiagnosticsMetricsClientOptionsBuilder MeterName(string name) - { - if (String.IsNullOrEmpty(name)) - throw new ArgumentNullException(nameof(name)); - Target.MeterName = name; - return this; - } - - public DiagnosticsMetricsClientOptionsBuilder MeterVersion(string version) - { - if (String.IsNullOrEmpty(version)) - throw new ArgumentNullException(nameof(version)); - Target.MeterVersion = version; - return this; - } -} diff --git a/src/Foundatio/Metrics/GaugeStat.cs b/src/Foundatio/Metrics/GaugeStat.cs deleted file mode 100644 index 92814269a..000000000 --- a/src/Foundatio/Metrics/GaugeStat.cs +++ /dev/null @@ -1,52 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; - -namespace Foundatio.Metrics; - -[DebuggerDisplay("Time: {Time} Max: {Max} Last: {Last}")] -public class GaugeStat -{ - public DateTime Time { get; set; } - public int Count { get; set; } - public double Total { get; set; } - public double Last { get; set; } - public double Min { get; set; } - public double Max { get; set; } - public double Average => Count > 0 ? Total / Count : 0; -} - -[DebuggerDisplay("Time: {StartTime}-{EndTime} Max: {Max} Last: {Last}")] -public class GaugeStatSummary -{ - public GaugeStatSummary(string name, ICollection stats, DateTime start, DateTime end) - { - Name = name; - Stats = stats; - Count = stats.Count > 0 ? Stats.Sum(s => s.Count) : 0; - Total = stats.Count > 0 ? Stats.Sum(s => s.Total) : 0; - Last = Stats.LastOrDefault()?.Last ?? 0; - Min = stats.Count > 0 ? Stats.Min(s => s.Min) : 0; - Max = stats.Count > 0 ? Stats.Max(s => s.Max) : 0; - StartTime = start; - EndTime = end; - Average = Count > 0 ? Total / Count : 0; - } - - public string Name { get; } - public DateTime StartTime { get; } - public DateTime EndTime { get; } - public ICollection Stats { get; } - public int Count { get; set; } - public double Total { get; set; } - public double Last { get; } - public double Min { get; set; } - public double Max { get; } - public double Average { get; } - - public override string ToString() - { - return $"Counter: {Name} Time: {StartTime}-{EndTime} Max: {Max} Last: {Last}"; - } -} diff --git a/src/Foundatio/Metrics/IMetricsClient.cs b/src/Foundatio/Metrics/IMetricsClient.cs deleted file mode 100644 index 7a192c318..000000000 --- a/src/Foundatio/Metrics/IMetricsClient.cs +++ /dev/null @@ -1,48 +0,0 @@ -#pragma warning disable 612, 618 - -using System; -using System.Threading.Tasks; -using Foundatio.Utility; - -namespace Foundatio.Metrics; - -[Obsolete("IMetricsClient will be removed, use System.Diagnostics.Metrics.Meter instead.")] -public interface IMetricsClient : IDisposable -{ - void Counter(string name, int value = 1); - void Gauge(string name, double value); - void Timer(string name, int milliseconds); -} - -public interface IBufferedMetricsClient : IMetricsClient -{ - Task FlushAsync(); -} - -public static class MetricsClientExtensions -{ - public static IDisposable StartTimer(this IMetricsClient client, string name) - { - return new MetricTimer(name, client); - } - - public static async Task TimeAsync(this IMetricsClient client, Func action, string name) - { - using (client.StartTimer(name)) - await action().AnyContext(); - } - - public static void Time(this IMetricsClient client, Action action, string name) - { - using (client.StartTimer(name)) - action(); - } - - public static async Task TimeAsync(this IMetricsClient client, Func> func, string name) - { - using (client.StartTimer(name)) - return await func().AnyContext(); - } -} - -#pragma warning restore 612, 618 diff --git a/src/Foundatio/Metrics/IMetricsClientStats.cs b/src/Foundatio/Metrics/IMetricsClientStats.cs deleted file mode 100644 index 4a2bf1fff..000000000 --- a/src/Foundatio/Metrics/IMetricsClientStats.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System; -using System.Threading.Tasks; -using Foundatio.Queues; -using Foundatio.Utility; - -namespace Foundatio.Metrics; - -public interface IMetricsClientStats -{ - Task GetCounterStatsAsync(string name, DateTime? utcStart = null, DateTime? utcEnd = null, int dataPoints = 20); - Task GetGaugeStatsAsync(string name, DateTime? utcStart = null, DateTime? utcEnd = null, int dataPoints = 20); - Task GetTimerStatsAsync(string name, DateTime? utcStart = null, DateTime? utcEnd = null, int dataPoints = 20); -} - -public static class MetricsClientStatsExtensions -{ - public static async Task GetCounterCountAsync(this IMetricsClientStats stats, string name, DateTime? utcStart = null, DateTime? utcEnd = null) - { - var result = await stats.GetCounterStatsAsync(name, utcStart, utcEnd, 1).AnyContext(); - return result.Count; - } - - public static async Task GetLastGaugeValueAsync(this IMetricsClientStats stats, string name, DateTime? utcStart = null, DateTime? utcEnd = null) - { - var result = await stats.GetGaugeStatsAsync(name, utcStart, utcEnd, 1).AnyContext(); - return result.Last; - } - - public static async Task GetQueueStatsAsync(this IMetricsClientStats stats, string name, string subMetricName = null, DateTime? utcStart = null, DateTime? utcEnd = null, int dataPoints = 20) - { - if (subMetricName == null) - subMetricName = String.Empty; - else - subMetricName = "." + subMetricName; - - var countTask = stats.GetGaugeStatsAsync($"{name}.count", utcStart, utcEnd, dataPoints); - var workingTask = stats.GetGaugeStatsAsync($"{name}.working", utcStart, utcEnd, dataPoints); - var deadletterTask = stats.GetGaugeStatsAsync($"{name}.deadletter", utcStart, utcEnd, dataPoints); - var enqueuedTask = stats.GetCounterStatsAsync($"{name}{subMetricName}.enqueued", utcStart, utcEnd, dataPoints); - var queueTimeTask = stats.GetTimerStatsAsync($"{name}{subMetricName}.queuetime", utcStart, utcEnd, dataPoints); - var dequeuedTask = stats.GetCounterStatsAsync($"{name}{subMetricName}.dequeued", utcStart, utcEnd, dataPoints); - var completedTask = stats.GetCounterStatsAsync($"{name}{subMetricName}.completed", utcStart, utcEnd, dataPoints); - var abandonedTask = stats.GetCounterStatsAsync($"{name}{subMetricName}.abandoned", utcStart, utcEnd, dataPoints); - var processTimeTask = stats.GetTimerStatsAsync($"{name}{subMetricName}.processtime", utcStart, utcEnd, dataPoints); - - await Task.WhenAll(countTask, workingTask, deadletterTask, enqueuedTask, queueTimeTask, dequeuedTask, completedTask, abandonedTask, processTimeTask).AnyContext(); - - return new QueueStatSummary - { - Count = countTask.Result, - Working = workingTask.Result, - Deadletter = deadletterTask.Result, - Enqueued = enqueuedTask.Result, - QueueTime = queueTimeTask.Result, - Dequeued = dequeuedTask.Result, - Completed = completedTask.Result, - Abandoned = abandonedTask.Result, - ProcessTime = processTimeTask.Result - }; - } -} diff --git a/src/Foundatio/Metrics/InMemoryMetricsClient.cs b/src/Foundatio/Metrics/InMemoryMetricsClient.cs deleted file mode 100644 index 3693ad06d..000000000 --- a/src/Foundatio/Metrics/InMemoryMetricsClient.cs +++ /dev/null @@ -1,20 +0,0 @@ -using Foundatio.Caching; - -namespace Foundatio.Metrics; - -public class InMemoryMetricsClient : CacheBucketMetricsClientBase -{ - public InMemoryMetricsClient() : this(o => o) { } - - public InMemoryMetricsClient(InMemoryMetricsClientOptions options) - : base(new InMemoryCacheClient(o => o.LoggerFactory(options?.LoggerFactory)), options) { } - - public InMemoryMetricsClient(Builder config) - : this(config(new InMemoryMetricsClientOptionsBuilder()).Build()) { } - - public override void Dispose() - { - base.Dispose(); - _cache.Dispose(); - } -} diff --git a/src/Foundatio/Metrics/InMemoryMetricsClientOptions.cs b/src/Foundatio/Metrics/InMemoryMetricsClientOptions.cs deleted file mode 100644 index c4444de3b..000000000 --- a/src/Foundatio/Metrics/InMemoryMetricsClientOptions.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace Foundatio.Metrics; - -public class InMemoryMetricsClientOptions : SharedMetricsClientOptions { } - -public class InMemoryMetricsClientOptionsBuilder : SharedMetricsClientOptionsBuilder { } diff --git a/src/Foundatio/Metrics/MetricKey.cs b/src/Foundatio/Metrics/MetricKey.cs deleted file mode 100644 index 2d27055e1..000000000 --- a/src/Foundatio/Metrics/MetricKey.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System; - -namespace Foundatio.Metrics; - -public struct MetricKey : IEquatable -{ - public MetricKey(DateTime startTimeUtc, TimeSpan duration, string name) - { - StartTimeUtc = startTimeUtc; - Duration = duration; - Name = name; - } - - public DateTime StartTimeUtc { get; } - public TimeSpan Duration { get; } - public string Name { get; } - - public DateTime EndTimeUtc => StartTimeUtc.Add(Duration); - - public bool Equals(MetricKey other) - { - return StartTimeUtc == other.StartTimeUtc && Duration == other.Duration && String.Equals(Name, other.Name); - } - - public override bool Equals(object obj) - { - if (obj is null) - return false; - - return obj is MetricKey key && Equals(key); - } - - public override int GetHashCode() - { - unchecked - { - return (StartTimeUtc.GetHashCode() * 397) ^ (Duration.GetHashCode() * 397) ^ (Name?.GetHashCode() ?? 0); - } - } - - public static bool operator ==(MetricKey left, MetricKey right) - { - return left.Equals(right); - } - - public static bool operator !=(MetricKey left, MetricKey right) - { - return !left.Equals(right); - } -} diff --git a/src/Foundatio/Metrics/MetricTimer.cs b/src/Foundatio/Metrics/MetricTimer.cs deleted file mode 100644 index fb1e695dc..000000000 --- a/src/Foundatio/Metrics/MetricTimer.cs +++ /dev/null @@ -1,34 +0,0 @@ -#pragma warning disable 612, 618 - -using System; -using System.Diagnostics; - -namespace Foundatio.Metrics; - -public class MetricTimer : IDisposable -{ - private readonly string _name; - private readonly Stopwatch _stopWatch; - private bool _disposed; - private readonly IMetricsClient _client; - - public MetricTimer(string name, IMetricsClient client) - { - _name = name; - _client = client; - _stopWatch = Stopwatch.StartNew(); - } - - public void Dispose() - { - if (_disposed) - return; - - _disposed = true; - _stopWatch.Stop(); - _client.Timer(_name, (int)_stopWatch.ElapsedMilliseconds); - } -} - -#pragma warning restore 612, 618 - diff --git a/src/Foundatio/Metrics/NullMetricsClient.cs b/src/Foundatio/Metrics/NullMetricsClient.cs deleted file mode 100644 index 4f0752a71..000000000 --- a/src/Foundatio/Metrics/NullMetricsClient.cs +++ /dev/null @@ -1,14 +0,0 @@ -#pragma warning disable 612, 618 - -namespace Foundatio.Metrics; - -public class NullMetricsClient : IMetricsClient -{ - public static readonly IMetricsClient Instance = new NullMetricsClient(); - public void Counter(string name, int value = 1) { } - public void Gauge(string name, double value) { } - public void Timer(string name, int milliseconds) { } - public void Dispose() { } -} - -#pragma warning restore 612, 618 diff --git a/src/Foundatio/Metrics/SharedMetricsClientOptions.cs b/src/Foundatio/Metrics/SharedMetricsClientOptions.cs deleted file mode 100644 index 22cb23cea..000000000 --- a/src/Foundatio/Metrics/SharedMetricsClientOptions.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace Foundatio.Metrics; - -public class SharedMetricsClientOptions : SharedOptions -{ - public bool Buffered { get; set; } = true; - public string Prefix { get; set; } -} - -public class SharedMetricsClientOptionsBuilder : SharedOptionsBuilder - where TOption : SharedMetricsClientOptions, new() - where TBuilder : SharedMetricsClientOptionsBuilder -{ - public TBuilder Buffered(bool buffered) - { - Target.Buffered = buffered; - return (TBuilder)this; - } - - public TBuilder Prefix(string prefix) - { - Target.Prefix = prefix; - return (TBuilder)this; - } - - public TBuilder EnableBuffer() => Buffered(true); - - public TBuilder DisableBuffer() => Buffered(false); -} diff --git a/src/Foundatio/Metrics/StatsDMetricsClient.cs b/src/Foundatio/Metrics/StatsDMetricsClient.cs deleted file mode 100644 index 620eae4b0..000000000 --- a/src/Foundatio/Metrics/StatsDMetricsClient.cs +++ /dev/null @@ -1,151 +0,0 @@ -using System; -using System.Diagnostics.CodeAnalysis; -using System.Globalization; -using System.Net; -using System.Net.Sockets; -using System.Runtime.InteropServices; -using System.Text; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; - -namespace Foundatio.Metrics; - -[SuppressMessage("ReSharper", "InconsistentlySynchronizedField")] -[Obsolete("StatsDMetricsClient will be removed, use System.Diagnostics.Metrics.Meter instead.")] -public class StatsDMetricsClient : IMetricsClient -{ - private readonly object _lock = new(); - private Socket _socket; - private readonly IPEndPoint _endPoint; - private readonly StatsDMetricsClientOptions _options; - private readonly ILogger _logger; - - public StatsDMetricsClient(StatsDMetricsClientOptions options) - { - _options = options; - _logger = options.LoggerFactory?.CreateLogger() ?? NullLogger.Instance; - _endPoint = GetIPEndPointFromHostName(options.ServerName, options.Port, false); - - if (!String.IsNullOrEmpty(options.Prefix)) - options.Prefix = options.Prefix.EndsWith(".") ? options.Prefix : String.Concat(options.Prefix, "."); - } - - public StatsDMetricsClient(Builder config) - : this(config(new StatsDMetricsClientOptionsBuilder()).Build()) { } - - public void Counter(string name, int value = 1) - { - Send(BuildMetric("c", name, value.ToString(CultureInfo.InvariantCulture))); - } - - public void Gauge(string name, double value) - { - Send(BuildMetric("g", name, value.ToString(CultureInfo.InvariantCulture))); - } - - public void Timer(string name, int milliseconds) - { - Send(BuildMetric("ms", name, milliseconds.ToString(CultureInfo.InvariantCulture))); - } - - private string BuildMetric(string type, string statName, string value) - { - return String.Concat(_options.Prefix, statName, ":", value, "|", type); - } - - private void Send(string metric) - { - if (String.IsNullOrEmpty(metric)) - return; - - try - { - var data = Encoding.ASCII.GetBytes(metric); - - EnsureSocket(); - lock (_lock) - { - _logger.LogTrace("Sending metric: {Metric}", metric); - _socket.SendTo(data, _endPoint); - } - } - catch (Exception ex) - { - _logger.LogError(ex, "An error occurred while sending the metrics: {Message}", ex.Message); - CloseSocket(); - } - } - - private void EnsureSocket() - { - _logger.LogTrace("EnsureSocket"); - if (_socket != null) - return; - - lock (_lock) - { - if (_socket != null) - return; - - _logger.LogTrace("Creating socket"); - _socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); - if (!RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - _socket.SendBufferSize = 0; - } - } - - private void CloseSocket() - { - _logger.LogTrace("CloseSocket"); - - if (_socket == null) - return; - - lock (_lock) - { - if (_socket == null) - return; - - _logger.LogTrace("Closing socket"); - try - { - _socket.Close(); - } - catch (Exception ex) - { - _logger.LogError(ex, "An error occurred while calling Close() on the socket"); - } - finally - { - _socket = null; - } - } - } - - private IPEndPoint GetIPEndPointFromHostName(string hostName, int port, bool throwIfMoreThanOneIP) - { - var addresses = Dns.GetHostAddresses(hostName); - if (addresses.Length == 0) - { - throw new ArgumentException( - "Unable to retrieve address from specified host name.", - nameof(hostName) - ); - } - - if (throwIfMoreThanOneIP && addresses.Length > 1) - { - throw new ArgumentException( - "There is more that one IP address to the specified host.", - nameof(hostName) - ); - } - - return new IPEndPoint(addresses[0], port); - } - - public void Dispose() - { - CloseSocket(); - } -} diff --git a/src/Foundatio/Metrics/StatsDMetricsClientOptions.cs b/src/Foundatio/Metrics/StatsDMetricsClientOptions.cs deleted file mode 100644 index 53a5ccd62..000000000 --- a/src/Foundatio/Metrics/StatsDMetricsClientOptions.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; - -namespace Foundatio.Metrics; - -public class StatsDMetricsClientOptions : SharedMetricsClientOptions -{ - public string ServerName { get; set; } - public int Port { get; set; } = 8125; -} - -public class StatsDMetricsClientOptionsBuilder : SharedMetricsClientOptionsBuilder -{ - public StatsDMetricsClientOptionsBuilder Server(string serverName, int port = 8125) - { - if (String.IsNullOrEmpty(serverName)) - throw new ArgumentNullException(nameof(serverName)); - Target.ServerName = serverName; - Target.Port = port; - return this; - } -} diff --git a/src/Foundatio/Metrics/TimingStat.cs b/src/Foundatio/Metrics/TimingStat.cs deleted file mode 100644 index 00023e915..000000000 --- a/src/Foundatio/Metrics/TimingStat.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; - -namespace Foundatio.Metrics; - -[DebuggerDisplay("Time: {Time} Count: {Count} Min: {MinDuration} Max: {MaxDuration} Total: {TotalDuration} Avg: {AverageDuration}")] -public class TimingStat -{ - public DateTime Time { get; set; } - public int Count { get; set; } - public long TotalDuration { get; set; } - public int MinDuration { get; set; } - public int MaxDuration { get; set; } - public double AverageDuration => Count > 0 ? (double)TotalDuration / Count : 0; -} - -[DebuggerDisplay("Time: {StartTime}-{EndTime} Count: {Count} Min: {MinDuration} Max: {MaxDuration} Total: {TotalDuration} Avg: {AverageDuration}")] -public class TimingStatSummary -{ - public TimingStatSummary(string name, ICollection stats, DateTime start, DateTime end) - { - Name = name; - Stats = stats; - Count = stats.Count > 0 ? Stats.Sum(s => s.Count) : 0; - MinDuration = stats.Count > 0 ? Stats.Min(s => s.MinDuration) : 0; - MaxDuration = stats.Count > 0 ? Stats.Max(s => s.MaxDuration) : 0; - TotalDuration = stats.Count > 0 ? Stats.Sum(s => s.TotalDuration) : 0; - AverageDuration = Count > 0 ? (double)TotalDuration / Count : 0; - StartTime = start; - EndTime = end; - } - - public string Name { get; } - public DateTime StartTime { get; } - public DateTime EndTime { get; } - public ICollection Stats { get; } - public int Count { get; } - public int MinDuration { get; } - public int MaxDuration { get; } - public long TotalDuration { get; } - public double AverageDuration { get; } - - public override string ToString() - { - return $"Timing: {Name} Time: {StartTime}-{EndTime} Count: {Count} Min: {MinDuration} Max: {MaxDuration} Total: {TotalDuration} Avg: {AverageDuration}"; - } -} diff --git a/src/Foundatio/Queues/IQueueActivity.cs b/src/Foundatio/Queues/IQueueActivity.cs index ea16697f5..5389d001e 100644 --- a/src/Foundatio/Queues/IQueueActivity.cs +++ b/src/Foundatio/Queues/IQueueActivity.cs @@ -4,6 +4,6 @@ namespace Foundatio.Queues; public interface IQueueActivity { - DateTime? LastEnqueueActivity { get; } - DateTime? LastDequeueActivity { get; } + DateTimeOffset? LastEnqueueActivity { get; } + DateTimeOffset? LastDequeueActivity { get; } } diff --git a/src/Foundatio/Queues/InMemoryQueue.cs b/src/Foundatio/Queues/InMemoryQueue.cs index 3b1ec9d3d..173053a78 100644 --- a/src/Foundatio/Queues/InMemoryQueue.cs +++ b/src/Foundatio/Queues/InMemoryQueue.cs @@ -91,7 +91,7 @@ protected override async Task EnqueueImplAsync(T data, QueueEntryOptions if (!await OnEnqueuingAsync(data, options).AnyContext()) return null; - var entry = new QueueEntry(id, options?.CorrelationId, data.DeepClone(), this, SystemClock.UtcNow, 0); + var entry = new QueueEntry(id, options?.CorrelationId, data.DeepClone(), this, _timeProvider.GetUtcNow().UtcDateTime, 0); entry.Properties.AddRange(options?.Properties); Interlocked.Increment(ref _enqueuedCount); @@ -107,7 +107,7 @@ protected override async Task EnqueueImplAsync(T data, QueueEntryOptions await OnEnqueuedAsync(entry).AnyContext(); _logger.LogTrace("Enqueue done"); - }, _queueDisposedCancellationTokenSource.Token); + }, _timeProvider, _queueDisposedCancellationTokenSource.Token); return id; } @@ -165,7 +165,7 @@ protected override void StartWorkingImpl(Func, CancellationToken, { try { - await Run.WithRetriesAsync(() => queueEntry.AbandonAsync(), 3, TimeSpan.Zero, cancellationToken).AnyContext(); + await Run.WithRetriesAsync(() => queueEntry.AbandonAsync(), 3, TimeSpan.Zero, _timeProvider, cancellationToken).AnyContext(); } catch (Exception abandonEx) { @@ -222,10 +222,10 @@ protected override async Task> DequeueImplAsync(CancellationToken if (!_queue.TryDequeue(out var entry) || entry == null) return null; - ScheduleNextMaintenance(SystemClock.UtcNow.Add(_options.WorkItemTimeout)); + ScheduleNextMaintenance(_timeProvider.GetUtcNow().UtcDateTime.Add(_options.WorkItemTimeout)); entry.Attempts++; - entry.DequeuedTimeUtc = SystemClock.UtcNow; + entry.DequeuedTimeUtc = _timeProvider.GetUtcNow().UtcDateTime; if (!_dequeued.TryAdd(entry.Id, entry)) throw new Exception("Unable to add item to the dequeued list"); @@ -246,7 +246,7 @@ public override async Task RenewLockAsync(IQueueEntry queueEntry) if (!_dequeued.TryGetValue(queueEntry.Id, out var targetEntry)) return; - targetEntry.RenewedTimeUtc = SystemClock.UtcNow; + targetEntry.RenewedTimeUtc = _timeProvider.GetUtcNow().UtcDateTime; await OnLockRenewedAsync(queueEntry).AnyContext(); _logger.LogTrace("Renew lock done: {Id}", queueEntry.Id); @@ -312,7 +312,7 @@ public override async Task AbandonAsync(IQueueEntry queueEntry) if (_options.RetryDelay > TimeSpan.Zero) { _logger.LogTrace("Adding item to wait list for future retry: {Id}", queueEntry.Id); - var unawaited = Run.DelayedAsync(GetRetryDelay(targetEntry.Attempts), () => RetryAsync(targetEntry), _queueDisposedCancellationTokenSource.Token); + var unawaited = Run.DelayedAsync(GetRetryDelay(targetEntry.Attempts), () => RetryAsync(targetEntry), _timeProvider, _queueDisposedCancellationTokenSource.Token); } else { @@ -368,8 +368,8 @@ public override Task DeleteQueueAsync() protected override async Task DoMaintenanceAsync() { - var utcNow = SystemClock.UtcNow; - var minAbandonAt = DateTime.MaxValue; + var utcNow = _timeProvider.GetUtcNow(); + var minAbandonAt = DateTimeOffset.MaxValue; try { @@ -395,7 +395,7 @@ public override Task DeleteQueueAsync() // Add a tiny buffer just in case the schedule next timer fires early. // The system clock typically has a resolution of 10-15 milliseconds, so timers cannot be more accurate than this resolution. - return minAbandonAt.SafeAdd(TimeSpan.FromMilliseconds(15)); + return minAbandonAt.UtcDateTime.SafeAdd(TimeSpan.FromMilliseconds(15)); } public override void Dispose() diff --git a/src/Foundatio/Queues/MetricsQueueBehavior.cs b/src/Foundatio/Queues/MetricsQueueBehavior.cs deleted file mode 100644 index 4fd99e83e..000000000 --- a/src/Foundatio/Queues/MetricsQueueBehavior.cs +++ /dev/null @@ -1,157 +0,0 @@ -using System; -using System.Threading.Tasks; -using Foundatio.Metrics; -using Foundatio.Utility; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Logging.Abstractions; - -namespace Foundatio.Queues; - -[Obsolete("MetricsQueueBehavior is no longer needed. Metrics are built into queues.")] -public class MetricsQueueBehavior : QueueBehaviorBase where T : class -{ - private readonly string _metricsPrefix; - private readonly IMetricsClient _metricsClient; - private readonly ILogger _logger; - private readonly ScheduledTimer _timer; - private readonly TimeSpan _reportInterval; - - public MetricsQueueBehavior(IMetricsClient metrics, string metricsPrefix = null, TimeSpan? reportCountsInterval = null, ILoggerFactory loggerFactory = null) - { - _logger = loggerFactory?.CreateLogger>() ?? NullLogger>.Instance; - _metricsClient = metrics ?? NullMetricsClient.Instance; - - if (!reportCountsInterval.HasValue) - reportCountsInterval = TimeSpan.FromMilliseconds(500); - - _reportInterval = reportCountsInterval.Value > TimeSpan.Zero ? reportCountsInterval.Value : TimeSpan.FromMilliseconds(250); - if (!String.IsNullOrEmpty(metricsPrefix) && !metricsPrefix.EndsWith(".")) - metricsPrefix += "."; - - metricsPrefix += typeof(T).Name.ToLowerInvariant(); - _metricsPrefix = metricsPrefix; - _timer = new ScheduledTimer(ReportQueueCountAsync, loggerFactory: loggerFactory); - } - - private async Task ReportQueueCountAsync() - { - try - { - var stats = await _queue.GetQueueStatsAsync().AnyContext(); - _logger.LogTrace("Reporting queue count"); - - _metricsClient.Gauge(GetFullMetricName("count"), stats.Queued); - _metricsClient.Gauge(GetFullMetricName("working"), stats.Working); - _metricsClient.Gauge(GetFullMetricName("deadletter"), stats.Deadletter); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error reporting queue metrics"); - } - - return null; - } - - protected override Task OnEnqueued(object sender, EnqueuedEventArgs enqueuedEventArgs) - { - _timer.ScheduleNext(SystemClock.UtcNow.Add(_reportInterval)); - - string subMetricName = GetSubMetricName(enqueuedEventArgs.Entry.Value); - if (!String.IsNullOrEmpty(subMetricName)) - _metricsClient.Counter(GetFullMetricName(subMetricName, "enqueued")); - - _metricsClient.Counter(GetFullMetricName("enqueued")); - return Task.CompletedTask; - } - - protected override Task OnDequeued(object sender, DequeuedEventArgs dequeuedEventArgs) - { - _timer.ScheduleNext(SystemClock.UtcNow.Add(_reportInterval)); - - var metadata = dequeuedEventArgs.Entry as IQueueEntryMetadata; - string subMetricName = GetSubMetricName(dequeuedEventArgs.Entry.Value); - - if (!String.IsNullOrEmpty(subMetricName)) - _metricsClient.Counter(GetFullMetricName(subMetricName, "dequeued")); - _metricsClient.Counter(GetFullMetricName("dequeued")); - - if (metadata == null || metadata.EnqueuedTimeUtc == DateTime.MinValue || metadata.DequeuedTimeUtc == DateTime.MinValue) - return Task.CompletedTask; - - var start = metadata.EnqueuedTimeUtc; - var end = metadata.DequeuedTimeUtc; - int time = (int)(end - start).TotalMilliseconds; - - if (!String.IsNullOrEmpty(subMetricName)) - _metricsClient.Timer(GetFullMetricName(subMetricName, "queuetime"), time); - _metricsClient.Timer(GetFullMetricName("queuetime"), time); - - return Task.CompletedTask; - } - - protected override Task OnCompleted(object sender, CompletedEventArgs completedEventArgs) - { - _timer.ScheduleNext(SystemClock.UtcNow.Add(_reportInterval)); - - if (!(completedEventArgs.Entry is IQueueEntryMetadata metadata)) - return Task.CompletedTask; - - string subMetricName = GetSubMetricName(completedEventArgs.Entry.Value); - if (!String.IsNullOrEmpty(subMetricName)) - _metricsClient.Counter(GetFullMetricName(subMetricName, "completed")); - _metricsClient.Counter(GetFullMetricName("completed")); - - int time = (int)metadata.ProcessingTime.TotalMilliseconds; - if (!String.IsNullOrEmpty(subMetricName)) - _metricsClient.Timer(GetFullMetricName(subMetricName, "processtime"), time); - _metricsClient.Timer(GetFullMetricName("processtime"), time); - - int totalTime = (int)metadata.TotalTime.TotalMilliseconds; - if (!String.IsNullOrEmpty(subMetricName)) - _metricsClient.Timer(GetFullMetricName(subMetricName, "totaltime"), totalTime); - _metricsClient.Timer(GetFullMetricName("totaltime"), totalTime); - - return Task.CompletedTask; - } - - protected override Task OnAbandoned(object sender, AbandonedEventArgs abandonedEventArgs) - { - _timer.ScheduleNext(SystemClock.UtcNow.Add(_reportInterval)); - - if (!(abandonedEventArgs.Entry is IQueueEntryMetadata metadata)) - return Task.CompletedTask; - - string subMetricName = GetSubMetricName(abandonedEventArgs.Entry.Value); - if (!String.IsNullOrEmpty(subMetricName)) - _metricsClient.Counter(GetFullMetricName(subMetricName, "abandoned")); - _metricsClient.Counter(GetFullMetricName("abandoned")); - - int time = (int)metadata.ProcessingTime.TotalMilliseconds; - if (!String.IsNullOrEmpty(subMetricName)) - _metricsClient.Timer(GetFullMetricName(subMetricName, "processtime"), time); - _metricsClient.Timer(GetFullMetricName("processtime"), time); - return Task.CompletedTask; - } - - protected string GetSubMetricName(T data) - { - var haveStatName = data as IHaveSubMetricName; - return haveStatName?.SubMetricName; - } - - protected string GetFullMetricName(string name) - { - return String.Concat(_metricsPrefix, ".", name); - } - - protected string GetFullMetricName(string customMetricName, string name) - { - return String.IsNullOrEmpty(customMetricName) ? GetFullMetricName(name) : String.Concat(_metricsPrefix, ".", customMetricName.ToLower(), ".", name); - } - - public override void Dispose() - { - _timer?.Dispose(); - base.Dispose(); - } -} diff --git a/src/Foundatio/Queues/QueueBase.cs b/src/Foundatio/Queues/QueueBase.cs index 8886473ed..2231b429d 100644 --- a/src/Foundatio/Queues/QueueBase.cs +++ b/src/Foundatio/Queues/QueueBase.cs @@ -13,19 +13,19 @@ namespace Foundatio.Queues; -public abstract class QueueBase : MaintenanceBase, IQueue, IQueueActivity where T : class where TOptions : SharedQueueOptions +public abstract class QueueBase : MaintenanceBase, IQueue, IHaveTimeProvider, IQueueActivity where T : class where TOptions : SharedQueueOptions { protected readonly TOptions _options; private readonly string _metricsPrefix; protected readonly ISerializer _serializer; - private readonly Counter _enqueuedCounter; - private readonly Counter _dequeuedCounter; + private readonly Counter _enqueuedCounter; + private readonly Counter _dequeuedCounter; private readonly Histogram _queueTimeHistogram; - private readonly Counter _completedCounter; + private readonly Counter _completedCounter; private readonly Histogram _processTimeHistogram; private readonly Histogram _totalTimeHistogram; - private readonly Counter _abandonedCounter; + private readonly Counter _abandonedCounter; #pragma warning disable IDE0052 // Remove unread private members private readonly ObservableGauge _countGauge; private readonly ObservableGauge _workingGauge; @@ -37,7 +37,7 @@ public abstract class QueueBase : MaintenanceBase, IQueue, IQueu protected readonly CancellationTokenSource _queueDisposedCancellationTokenSource; private bool _isDisposed; - protected QueueBase(TOptions options) : base(options?.LoggerFactory) + protected QueueBase(TOptions options) : base(options?.TimeProvider, options?.LoggerFactory) { _options = options ?? throw new ArgumentNullException(nameof(options)); _metricsPrefix = $"foundatio.{typeof(T).Name.ToLowerInvariant()}"; @@ -50,13 +50,13 @@ protected QueueBase(TOptions options) : base(options?.LoggerFactory) _queueDisposedCancellationTokenSource = new CancellationTokenSource(); // setup meters - _enqueuedCounter = FoundatioDiagnostics.Meter.CreateCounter(GetFullMetricName("enqueued"), description: "Number of enqueued items"); - _dequeuedCounter = FoundatioDiagnostics.Meter.CreateCounter(GetFullMetricName("dequeued"), description: "Number of dequeued items"); + _enqueuedCounter = FoundatioDiagnostics.Meter.CreateCounter(GetFullMetricName("enqueued"), description: "Number of enqueued items"); + _dequeuedCounter = FoundatioDiagnostics.Meter.CreateCounter(GetFullMetricName("dequeued"), description: "Number of dequeued items"); _queueTimeHistogram = FoundatioDiagnostics.Meter.CreateHistogram(GetFullMetricName("queuetime"), description: "Time in queue", unit: "ms"); - _completedCounter = FoundatioDiagnostics.Meter.CreateCounter(GetFullMetricName("completed"), description: "Number of completed items"); + _completedCounter = FoundatioDiagnostics.Meter.CreateCounter(GetFullMetricName("completed"), description: "Number of completed items"); _processTimeHistogram = FoundatioDiagnostics.Meter.CreateHistogram(GetFullMetricName("processtime"), description: "Time to process items", unit: "ms"); _totalTimeHistogram = FoundatioDiagnostics.Meter.CreateHistogram(GetFullMetricName("totaltime"), description: "Total time in queue", unit: "ms"); - _abandonedCounter = FoundatioDiagnostics.Meter.CreateCounter(GetFullMetricName("abandoned"), description: "Number of abandoned items"); + _abandonedCounter = FoundatioDiagnostics.Meter.CreateCounter(GetFullMetricName("abandoned"), description: "Number of abandoned items"); var queueMetricValues = new InstrumentsValues(() => { @@ -77,9 +77,10 @@ protected QueueBase(TOptions options) : base(options?.LoggerFactory) } public string QueueId { get; protected set; } - public DateTime? LastEnqueueActivity { get; protected set; } - public DateTime? LastDequeueActivity { get; protected set; } + public DateTimeOffset? LastEnqueueActivity { get; protected set; } + public DateTimeOffset? LastDequeueActivity { get; protected set; } ISerializer IHaveSerializer.Serializer => _serializer; + TimeProvider IHaveTimeProvider.TimeProvider => _timeProvider; public void AttachBehavior(IQueueBehavior behavior) { @@ -97,7 +98,7 @@ public async Task EnqueueAsync(T data, QueueEntryOptions options = null) { await EnsureQueueCreatedAsync(_queueDisposedCancellationTokenSource.Token).AnyContext(); - LastEnqueueActivity = SystemClock.UtcNow; + LastEnqueueActivity = _timeProvider.GetUtcNow(); options ??= new QueueEntryOptions(); return await EnqueueImplAsync(data, options).AnyContext(); @@ -108,7 +109,7 @@ public async Task> DequeueAsync(CancellationToken cancellationTok { await EnsureQueueCreatedAsync(_queueDisposedCancellationTokenSource.Token).AnyContext(); - LastDequeueActivity = SystemClock.UtcNow; + LastDequeueActivity = _timeProvider.GetUtcNow(); using var linkedCancellationTokenSource = GetLinkedDisposableCancellationTokenSource(cancellationToken); return await DequeueImplAsync(linkedCancellationTokenSource.Token).AnyContext(); } @@ -180,7 +181,7 @@ protected virtual async Task OnEnqueuingAsync(T data, QueueEntryOptions op protected virtual Task OnEnqueuedAsync(IQueueEntry entry) { - LastEnqueueActivity = SystemClock.UtcNow; + LastEnqueueActivity = _timeProvider.GetUtcNow(); var tags = GetQueueEntryTags(entry); _enqueuedCounter.Add(1, tags); @@ -198,7 +199,7 @@ protected virtual Task OnEnqueuedAsync(IQueueEntry entry) protected virtual Task OnDequeuedAsync(IQueueEntry entry) { - LastDequeueActivity = SystemClock.UtcNow; + LastDequeueActivity = _timeProvider.GetUtcNow(); var tags = GetQueueEntryTags(entry); _dequeuedCounter.Add(1, tags); @@ -209,7 +210,7 @@ protected virtual Task OnDequeuedAsync(IQueueEntry entry) { var start = metadata.EnqueuedTimeUtc; var end = metadata.DequeuedTimeUtc; - int time = (int)(end - start).TotalMilliseconds; + double time = (end - start).TotalMilliseconds; _queueTimeHistogram.Record(time, tags); RecordSubHistogram(entry.Value, "queuetime", time, tags); @@ -232,7 +233,7 @@ protected virtual TagList GetQueueEntryTags(IQueueEntry entry) protected virtual Task OnLockRenewedAsync(IQueueEntry entry) { - LastDequeueActivity = SystemClock.UtcNow; + LastDequeueActivity = _timeProvider.GetUtcNow(); var lockRenewed = LockRenewed; if (lockRenewed == null) @@ -246,7 +247,7 @@ protected virtual Task OnLockRenewedAsync(IQueueEntry entry) protected virtual async Task OnCompletedAsync(IQueueEntry entry) { - var now = SystemClock.UtcNow; + var now = _timeProvider.GetUtcNow(); LastDequeueActivity = now; var tags = GetQueueEntryTags(entry); @@ -281,7 +282,7 @@ protected virtual async Task OnCompletedAsync(IQueueEntry entry) protected virtual async Task OnAbandonedAsync(IQueueEntry entry) { - LastDequeueActivity = SystemClock.UtcNow; + LastDequeueActivity = _timeProvider.GetUtcNow(); var tags = GetQueueEntryTags(entry); _abandonedCounter.Add(1, tags); @@ -289,7 +290,7 @@ protected virtual async Task OnAbandonedAsync(IQueueEntry entry) if (entry is QueueEntry metadata && metadata.DequeuedTimeUtc > DateTime.MinValue) { - metadata.ProcessingTime = SystemClock.UtcNow.Subtract(metadata.DequeuedTimeUtc); + metadata.ProcessingTime = _timeProvider.GetUtcNow().Subtract(metadata.DequeuedTimeUtc); _processTimeHistogram.Record((int)metadata.ProcessingTime.TotalMilliseconds, tags); RecordSubHistogram(entry.Value, "processtime", (int)metadata.ProcessingTime.TotalMilliseconds, tags); } @@ -307,7 +308,7 @@ protected string GetSubMetricName(T data) return haveStatName?.SubMetricName; } - protected readonly ConcurrentDictionary> _counters = new(); + protected readonly ConcurrentDictionary> _counters = new(); private void IncrementSubCounter(T data, string name, in TagList tags) { if (data is not IHaveSubMetricName) @@ -318,11 +319,11 @@ private void IncrementSubCounter(T data, string name, in TagList tags) return; var fullName = GetFullMetricName(subMetricName, name); - _counters.GetOrAdd(fullName, FoundatioDiagnostics.Meter.CreateCounter(fullName)).Add(1, tags); + _counters.GetOrAdd(fullName, FoundatioDiagnostics.Meter.CreateCounter(fullName)).Add(1, tags); } - protected readonly ConcurrentDictionary> _histograms = new(); - private void RecordSubHistogram(T data, string name, int value, in TagList tags) + protected readonly ConcurrentDictionary> _histograms = new(); + private void RecordSubHistogram(T data, string name, double value, in TagList tags) { if (data is not IHaveSubMetricName) return; @@ -332,7 +333,7 @@ private void RecordSubHistogram(T data, string name, int value, in TagList tags) return; var fullName = GetFullMetricName(subMetricName, name); - _histograms.GetOrAdd(fullName, FoundatioDiagnostics.Meter.CreateHistogram(fullName)).Record(value, tags); + _histograms.GetOrAdd(fullName, FoundatioDiagnostics.Meter.CreateHistogram(fullName)).Record(value, tags); } protected string GetFullMetricName(string name) diff --git a/src/Foundatio/Queues/QueueEntry.cs b/src/Foundatio/Queues/QueueEntry.cs index 455810559..d850ba210 100644 --- a/src/Foundatio/Queues/QueueEntry.cs +++ b/src/Foundatio/Queues/QueueEntry.cs @@ -19,7 +19,7 @@ public QueueEntry(string id, string correlationId, T value, IQueue queue, Dat _queue = queue; EnqueuedTimeUtc = enqueuedTimeUtc; Attempts = attempts; - DequeuedTimeUtc = RenewedTimeUtc = SystemClock.UtcNow; + DequeuedTimeUtc = RenewedTimeUtc = _queue.GetTimeProvider().GetUtcNow().UtcDateTime; } public string Id { get; } @@ -49,7 +49,7 @@ void IQueueEntry.MarkAbandoned() public Task RenewLockAsync() { - RenewedTimeUtc = SystemClock.UtcNow; + RenewedTimeUtc = _queue.GetTimeProvider().GetUtcNow().UtcDateTime; return _queue.RenewLockAsync(this); } diff --git a/src/Foundatio/Queues/QueueStatSummary.cs b/src/Foundatio/Queues/QueueStatSummary.cs deleted file mode 100644 index 509b957f4..000000000 --- a/src/Foundatio/Queues/QueueStatSummary.cs +++ /dev/null @@ -1,16 +0,0 @@ -using Foundatio.Metrics; - -namespace Foundatio.Queues; - -public class QueueStatSummary -{ - public GaugeStatSummary Count { get; set; } - public GaugeStatSummary Working { get; set; } - public GaugeStatSummary Deadletter { get; set; } - public CounterStatSummary Enqueued { get; set; } - public TimingStatSummary QueueTime { get; set; } - public CounterStatSummary Dequeued { get; set; } - public CounterStatSummary Completed { get; set; } - public CounterStatSummary Abandoned { get; set; } - public TimingStatSummary ProcessTime { get; set; } -} diff --git a/src/Foundatio/Storage/InMemoryFileStorage.cs b/src/Foundatio/Storage/InMemoryFileStorage.cs index f0449bd6d..cf66a64c3 100644 --- a/src/Foundatio/Storage/InMemoryFileStorage.cs +++ b/src/Foundatio/Storage/InMemoryFileStorage.cs @@ -19,6 +19,7 @@ public class InMemoryFileStorage : IFileStorage private readonly ConcurrentDictionary _storage = new(StringComparer.OrdinalIgnoreCase); private readonly ISerializer _serializer; protected readonly ILogger _logger; + private readonly TimeProvider _timeProvider; public InMemoryFileStorage() : this(o => o) { } @@ -30,6 +31,7 @@ public InMemoryFileStorage(InMemoryFileStorageOptions options) MaxFileSize = options.MaxFileSize; MaxFiles = options.MaxFiles; _serializer = options.Serializer ?? DefaultSerializer.Instance; + _timeProvider = options.TimeProvider; _logger = options.LoggerFactory?.CreateLogger(GetType()) ?? NullLogger.Instance; } @@ -136,14 +138,14 @@ private void AddOrUpdate(string path, byte[] contents) _storage.AddOrUpdate(normalizedPath, (new FileSpec { - Created = SystemClock.UtcNow, - Modified = SystemClock.UtcNow, + Created = _timeProvider.GetUtcNow().UtcDateTime, + Modified = _timeProvider.GetUtcNow().UtcDateTime, Path = normalizedPath, Size = contents.Length }, contents), (_, file) => (new FileSpec { Created = file.Spec.Created, - Modified = SystemClock.UtcNow, + Modified = _timeProvider.GetUtcNow().UtcDateTime, Path = file.Spec.Path, Size = contents.Length }, contents)); diff --git a/src/Foundatio/Utility/IHaveTimeProvider.cs b/src/Foundatio/Utility/IHaveTimeProvider.cs new file mode 100644 index 000000000..5714fcc49 --- /dev/null +++ b/src/Foundatio/Utility/IHaveTimeProvider.cs @@ -0,0 +1,16 @@ +using System; + +namespace Foundatio.Utility; + +public interface IHaveTimeProvider +{ + TimeProvider TimeProvider { get; } +} + +public static class TimeProviderExtensions +{ + public static TimeProvider GetTimeProvider(this object target) + { + return target is IHaveTimeProvider accessor ? accessor.TimeProvider ?? TimeProvider.System : TimeProvider.System; + } +} diff --git a/src/Foundatio/Utility/MaintenanceBase.cs b/src/Foundatio/Utility/MaintenanceBase.cs index 7d5c9efdd..bb419c12e 100644 --- a/src/Foundatio/Utility/MaintenanceBase.cs +++ b/src/Foundatio/Utility/MaintenanceBase.cs @@ -9,17 +9,19 @@ public class MaintenanceBase : IDisposable { private ScheduledTimer _maintenanceTimer; private readonly ILoggerFactory _loggerFactory; + protected readonly TimeProvider _timeProvider; protected readonly ILogger _logger; - public MaintenanceBase(ILoggerFactory loggerFactory) + public MaintenanceBase(TimeProvider timeProvider, ILoggerFactory loggerFactory) { + _timeProvider = timeProvider ?? TimeProvider.System; _loggerFactory = loggerFactory ?? NullLoggerFactory.Instance; _logger = _loggerFactory.CreateLogger(GetType()); } protected void InitializeMaintenance(TimeSpan? dueTime = null, TimeSpan? intervalTime = null) { - _maintenanceTimer = new ScheduledTimer(DoMaintenanceAsync, dueTime, intervalTime, _loggerFactory); + _maintenanceTimer = new ScheduledTimer(DoMaintenanceAsync, dueTime, intervalTime, _timeProvider, _loggerFactory); } protected void ScheduleNextMaintenance(DateTime utcDate) diff --git a/src/Foundatio/Utility/Run.cs b/src/Foundatio/Utility/Run.cs index f3b7ce1fe..47eb17ad8 100644 --- a/src/Foundatio/Utility/Run.cs +++ b/src/Foundatio/Utility/Run.cs @@ -8,15 +8,17 @@ namespace Foundatio.Utility; public static class Run { - public static Task DelayedAsync(TimeSpan delay, Func action, CancellationToken cancellationToken = default) + public static Task DelayedAsync(TimeSpan delay, Func action, TimeProvider timeProvider = null, CancellationToken cancellationToken = default) { + timeProvider ??= TimeProvider.System; + if (cancellationToken.IsCancellationRequested) return Task.CompletedTask; return Task.Run(async () => { if (delay.Ticks > 0) - await SystemClock.SleepAsync(delay, cancellationToken).AnyContext(); + await timeProvider.Delay(delay, cancellationToken).AnyContext(); if (cancellationToken.IsCancellationRequested) return; @@ -31,22 +33,23 @@ public static Task InParallelAsync(int iterations, Func work) return Task.WhenAll(Enumerable.Range(1, iterations).Select(i => Task.Run(() => work(i)))); } - public static Task WithRetriesAsync(Func action, int maxAttempts = 5, TimeSpan? retryInterval = null, CancellationToken cancellationToken = default, ILogger logger = null) + public static Task WithRetriesAsync(Func action, int maxAttempts = 5, TimeSpan? retryInterval = null, TimeProvider timeProvider = null, CancellationToken cancellationToken = default, ILogger logger = null) { return WithRetriesAsync(async () => { await action().AnyContext(); return null; - }, maxAttempts, retryInterval, cancellationToken, logger); + }, maxAttempts, retryInterval, timeProvider, cancellationToken, logger); } - public static async Task WithRetriesAsync(Func> action, int maxAttempts = 5, TimeSpan? retryInterval = null, CancellationToken cancellationToken = default, ILogger logger = null) + public static async Task WithRetriesAsync(Func> action, int maxAttempts = 5, TimeSpan? retryInterval = null, TimeProvider timeProvider = null, CancellationToken cancellationToken = default, ILogger logger = null) { if (action == null) throw new ArgumentNullException(nameof(action)); + timeProvider ??= TimeProvider.System; int attempts = 1; - var startTime = SystemClock.UtcNow; + var startTime = timeProvider.GetUtcNow(); int currentBackoffTime = _defaultBackoffIntervals[0]; if (retryInterval != null) currentBackoffTime = (int)retryInterval.Value.TotalMilliseconds; @@ -54,7 +57,7 @@ public static async Task WithRetriesAsync(Func> action, int maxAtt do { if (attempts > 1 && logger != null && logger.IsEnabled(LogLevel.Information)) - logger.LogInformation("Retrying {Attempts} attempt after {Delay:g}...", attempts.ToOrdinal(), SystemClock.UtcNow.Subtract(startTime)); + logger.LogInformation("Retrying {Attempts} attempt after {Delay:g}...", attempts.ToOrdinal(), timeProvider.GetUtcNow().Subtract(startTime)); try { @@ -68,7 +71,7 @@ public static async Task WithRetriesAsync(Func> action, int maxAtt if (logger != null && logger.IsEnabled(LogLevel.Error)) logger.LogError(ex, "Retry error: {Message}", ex.Message); - await SystemClock.SleepSafeAsync(currentBackoffTime, cancellationToken).AnyContext(); + await timeProvider.SafeDelay(TimeSpan.FromMilliseconds(currentBackoffTime), cancellationToken).AnyContext(); } if (retryInterval == null) @@ -79,5 +82,5 @@ public static async Task WithRetriesAsync(Func> action, int maxAtt throw new TaskCanceledException("Should not get here"); } - private static readonly int[] _defaultBackoffIntervals = new int[] { 100, 1000, 2000, 2000, 5000, 5000, 10000, 30000, 60000 }; + private static readonly int[] _defaultBackoffIntervals = [ 100, 1000, 2000, 2000, 5000, 5000, 10000, 30000, 60000 ]; } diff --git a/src/Foundatio/Utility/ScheduledTimer.cs b/src/Foundatio/Utility/ScheduledTimer.cs index b428c5c09..b48151db9 100644 --- a/src/Foundatio/Utility/ScheduledTimer.cs +++ b/src/Foundatio/Utility/ScheduledTimer.cs @@ -15,15 +15,17 @@ public class ScheduledTimer : IDisposable private readonly Timer _timer; private readonly ILogger _logger; private readonly Func> _timerCallback; + private readonly TimeProvider _timeProvider; private readonly TimeSpan _minimumInterval; private readonly AsyncLock _lock = new(); private bool _isRunning = false; private bool _shouldRunAgainImmediately = false; - public ScheduledTimer(Func> timerCallback, TimeSpan? dueTime = null, TimeSpan? minimumIntervalTime = null, ILoggerFactory loggerFactory = null) + public ScheduledTimer(Func> timerCallback, TimeSpan? dueTime = null, TimeSpan? minimumIntervalTime = null, TimeProvider timeProvider = null, ILoggerFactory loggerFactory = null) { _logger = loggerFactory?.CreateLogger() ?? NullLogger.Instance; _timerCallback = timerCallback ?? throw new ArgumentNullException(nameof(timerCallback)); + _timeProvider = timeProvider ?? TimeProvider.System; _minimumInterval = minimumIntervalTime ?? TimeSpan.Zero; int dueTimeMs = dueTime.HasValue ? (int)dueTime.Value.TotalMilliseconds : Timeout.Infinite; @@ -32,7 +34,7 @@ public ScheduledTimer(Func> timerCallback, TimeSpan? dueTime = n public void ScheduleNext(DateTime? utcDate = null) { - var utcNow = SystemClock.UtcNow; + var utcNow = _timeProvider.GetUtcNow().UtcDateTime; if (!utcDate.HasValue || utcDate.Value < utcNow) utcDate = utcNow; @@ -117,12 +119,12 @@ private async Task RunCallbackAsync() return; } - _last = SystemClock.UtcNow; - if (SystemClock.UtcNow < _next) + _last = _timeProvider.GetUtcNow().UtcDateTime; + if (_last < _next) { _logger.LogWarning("ScheduleNext RunCallbackAsync was called before next run time {NextRun:O}, setting next to current time and rescheduling", _next); nextTimeOverride = _next; - _next = SystemClock.UtcNow; + _next = _timeProvider.GetUtcNow().UtcDateTime; _shouldRunAgainImmediately = true; } } @@ -153,11 +155,11 @@ private async Task RunCallbackAsync() if (_minimumInterval > TimeSpan.Zero) { if (isTraceLogLevelEnabled) _logger.LogTrace("Sleeping for minimum interval: {Interval:g}", _minimumInterval); - await SystemClock.SleepAsync(_minimumInterval).AnyContext(); + await _timeProvider.Delay(_minimumInterval).AnyContext(); if (isTraceLogLevelEnabled) _logger.LogTrace("Finished sleeping"); } - var nextRun = SystemClock.UtcNow.AddMilliseconds(10); + var nextRun = _timeProvider.GetUtcNow().UtcDateTime.AddMilliseconds(10); if (nextRun < nextTimeOverride) nextRun = nextTimeOverride.Value; diff --git a/src/Foundatio/Utility/SharedOptions.cs b/src/Foundatio/Utility/SharedOptions.cs index 9eb89822b..8ffb31105 100644 --- a/src/Foundatio/Utility/SharedOptions.cs +++ b/src/Foundatio/Utility/SharedOptions.cs @@ -1,3 +1,4 @@ +using System; using Foundatio.Serializer; using Microsoft.Extensions.Logging; @@ -5,6 +6,7 @@ namespace Foundatio; public class SharedOptions { + public TimeProvider TimeProvider { get; set; } = TimeProvider.System; public ISerializer Serializer { get; set; } public ILoggerFactory LoggerFactory { get; set; } } @@ -13,6 +15,12 @@ public class SharedOptionsBuilder : OptionsBuilder where TOption : SharedOptions, new() where TBuilder : SharedOptionsBuilder { + public TBuilder TimeProvider(TimeProvider timeProvider) + { + Target.TimeProvider = timeProvider; + return (TBuilder)this; + } + public TBuilder Serializer(ISerializer serializer) { Target.Serializer = serializer; diff --git a/src/Foundatio/Utility/SystemClock.cs b/src/Foundatio/Utility/SystemClock.cs deleted file mode 100644 index 2cb2fad90..000000000 --- a/src/Foundatio/Utility/SystemClock.cs +++ /dev/null @@ -1,235 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace Foundatio.Utility; - -public interface ISystemClock -{ - DateTime Now(); - DateTime UtcNow(); - DateTimeOffset OffsetNow(); - DateTimeOffset OffsetUtcNow(); - void Sleep(int milliseconds); - Task SleepAsync(int milliseconds, CancellationToken ct); - TimeSpan TimeZoneOffset(); -} - -public class RealSystemClock : ISystemClock -{ - public static readonly RealSystemClock Instance = new(); - - public DateTime Now() => DateTime.Now; - public DateTime UtcNow() => DateTime.UtcNow; - public DateTimeOffset OffsetNow() => DateTimeOffset.Now; - public DateTimeOffset OffsetUtcNow() => DateTimeOffset.UtcNow; - public void Sleep(int milliseconds) => Thread.Sleep(milliseconds); - public Task SleepAsync(int milliseconds, CancellationToken ct) => Task.Delay(milliseconds, ct); - public TimeSpan TimeZoneOffset() => DateTimeOffset.Now.Offset; -} - -internal class TestSystemClockImpl : ISystemClock, IDisposable -{ - private DateTime? _fixedUtc = null; - private TimeSpan _offset = TimeSpan.Zero; - private TimeSpan _timeZoneOffset = DateTimeOffset.Now.Offset; - private bool _fakeSleep = false; - private ISystemClock _originalClock; - - public TestSystemClockImpl() { } - - public TestSystemClockImpl(ISystemClock originalTime) - { - _originalClock = originalTime; - } - - public DateTime UtcNow() => (_fixedUtc ?? DateTime.UtcNow).Add(_offset); - public DateTime Now() => new(UtcNow().Ticks + TimeZoneOffset().Ticks, DateTimeKind.Local); - public DateTimeOffset OffsetNow() => new(UtcNow().Ticks + TimeZoneOffset().Ticks, TimeZoneOffset()); - public DateTimeOffset OffsetUtcNow() => new(UtcNow().Ticks, TimeSpan.Zero); - public TimeSpan TimeZoneOffset() => _timeZoneOffset; - - public void SetTimeZoneOffset(TimeSpan offset) => _timeZoneOffset = offset; - public void AddTime(TimeSpan amount) => _offset = _offset.Add(amount); - public void SubtractTime(TimeSpan amount) => _offset = _offset.Subtract(amount); - public void UseFakeSleep() => _fakeSleep = true; - public void UseRealSleep() => _fakeSleep = false; - - public void Sleep(int milliseconds) - { - if (!_fakeSleep) - { - Thread.Sleep(milliseconds); - return; - } - - AddTime(TimeSpan.FromMilliseconds(milliseconds)); - Thread.Sleep(1); - } - - public Task SleepAsync(int milliseconds, CancellationToken ct) - { - if (!_fakeSleep) - return Task.Delay(milliseconds, ct); - - Sleep(milliseconds); - return Task.CompletedTask; - } - - public void Freeze() - { - SetFrozenTime(Now()); - } - - public void Unfreeze() - { - SetTime(Now()); - } - - public void SetFrozenTime(DateTime time) - { - SetTime(time, true); - } - - public void SetTime(DateTime time, bool freeze = false) - { - var now = DateTime.Now; - if (freeze) - { - if (time.Kind == DateTimeKind.Unspecified) - time = time.ToUniversalTime(); - - if (time.Kind == DateTimeKind.Utc) - { - _fixedUtc = time; - } - else if (time.Kind == DateTimeKind.Local) - { - _fixedUtc = new DateTime(time.Ticks - TimeZoneOffset().Ticks, DateTimeKind.Utc); - } - } - else - { - _fixedUtc = null; - - if (time.Kind == DateTimeKind.Unspecified) - time = time.ToUniversalTime(); - - if (time.Kind == DateTimeKind.Utc) - { - _offset = now.ToUniversalTime().Subtract(time); - } - else if (time.Kind == DateTimeKind.Local) - { - _offset = now.Subtract(time); - } - } - } - - public void Dispose() - { - if (_originalClock == null) - return; - - var originalClock = Interlocked.Exchange(ref _originalClock, null); - if (originalClock != null) - SystemClock.Instance = originalClock; - } - - public static TestSystemClockImpl Instance - { - get - { - if (!(SystemClock.Instance is TestSystemClockImpl testClock)) - throw new ArgumentException("You must first install TestSystemClock using TestSystemClock.Install"); - - return testClock; - } - } -} - -public class TestSystemClock -{ - public static void SetTimeZoneOffset(TimeSpan offset) => TestSystemClockImpl.Instance.SetTimeZoneOffset(offset); - public static void AddTime(TimeSpan amount) => TestSystemClockImpl.Instance.AddTime(amount); - public static void SubtractTime(TimeSpan amount) => TestSystemClockImpl.Instance.SubtractTime(amount); - public static void UseFakeSleep() => TestSystemClockImpl.Instance.UseFakeSleep(); - public static void UseRealSleep() => TestSystemClockImpl.Instance.UseRealSleep(); - public static void Freeze() => TestSystemClockImpl.Instance.Freeze(); - public static void Unfreeze() => TestSystemClockImpl.Instance.Unfreeze(); - public static void SetFrozenTime(DateTime time) => TestSystemClockImpl.Instance.SetFrozenTime(time); - public static void SetTime(DateTime time, bool freeze = false) => TestSystemClockImpl.Instance.SetTime(time, freeze); - - public static IDisposable Install() - { - var testClock = new TestSystemClockImpl(SystemClock.Instance); - SystemClock.Instance = testClock; - - return testClock; - } -} - -public static class SystemClock -{ - private static AsyncLocal _instance; - - public static ISystemClock Instance - { - get => _instance?.Value ?? RealSystemClock.Instance; - set - { - if (_instance == null) - _instance = new AsyncLocal(); - - _instance.Value = value; - } - } - - public static DateTime Now => Instance.Now(); - public static DateTime UtcNow => Instance.UtcNow(); - public static DateTimeOffset OffsetNow => Instance.OffsetNow(); - public static DateTimeOffset OffsetUtcNow => Instance.OffsetUtcNow(); - public static TimeSpan TimeZoneOffset => Instance.TimeZoneOffset(); - public static void Sleep(int milliseconds) => Instance.Sleep(milliseconds); - public static Task SleepAsync(int milliseconds, CancellationToken cancellationToken = default) - => Instance.SleepAsync(milliseconds, cancellationToken); - - #region Extensions - - public static void Sleep(TimeSpan delay) - => Instance.Sleep(delay); - - public static Task SleepAsync(TimeSpan delay, CancellationToken cancellationToken = default) - => Instance.SleepAsync(delay, cancellationToken); - - public static Task SleepSafeAsync(int milliseconds, CancellationToken cancellationToken = default) - { - return Instance.SleepSafeAsync(milliseconds, cancellationToken); - } - - public static Task SleepSafeAsync(TimeSpan delay, CancellationToken cancellationToken = default) - => Instance.SleepSafeAsync(delay, cancellationToken); - - #endregion -} - -public static class TimeExtensions -{ - public static void Sleep(this ISystemClock time, TimeSpan delay) - => time.Sleep((int)delay.TotalMilliseconds); - - public static Task SleepAsync(this ISystemClock time, TimeSpan delay, CancellationToken cancellationToken = default) - => time.SleepAsync((int)delay.TotalMilliseconds, cancellationToken); - - public static async Task SleepSafeAsync(this ISystemClock time, int milliseconds, CancellationToken cancellationToken = default) - { - try - { - await time.SleepAsync(milliseconds, cancellationToken).AnyContext(); - } - catch (OperationCanceledException) { } - } - - public static Task SleepSafeAsync(this ISystemClock time, TimeSpan delay, CancellationToken cancellationToken = default) - => time.SleepSafeAsync((int)delay.TotalMilliseconds, cancellationToken); -} diff --git a/tests/Foundatio.Tests/Caching/InMemoryCacheClientTests.cs b/tests/Foundatio.Tests/Caching/InMemoryCacheClientTests.cs index b88b3976f..48f366698 100644 --- a/tests/Foundatio.Tests/Caching/InMemoryCacheClientTests.cs +++ b/tests/Foundatio.Tests/Caching/InMemoryCacheClientTests.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Threading.Tasks; using Foundatio.Caching; -using Foundatio.Utility; using Microsoft.Extensions.Logging; using Xunit; using Xunit.Abstractions; @@ -174,7 +173,7 @@ public async Task CanSetMaxItems() Assert.Equal(10, cache.Count); Assert.False((await cache.GetAsync("test0")).HasValue); Assert.Equal(1, cache.Misses); - await SystemClock.SleepAsync(50); // keep the last access ticks from being the same for all items + await TimeProvider.System.Delay(TimeSpan.FromMilliseconds(50)); // keep the last access ticks from being the same for all items Assert.NotNull(await cache.GetAsync("test1")); Assert.Equal(1, cache.Hits); await cache.SetAsync("next2", 2); @@ -196,7 +195,7 @@ public async Task SetAllShouldExpire() await client.SetAllAsync(new Dictionary { { "test", "value" } }, expiry); // Add 1ms to the expiry to ensure the cache has expired as the delay window is not guaranteed to be exact. - await Task.Delay(expiry.Add(TimeSpan.FromMilliseconds(1))); + await Task.Delay(expiry.Add(TimeSpan.FromMilliseconds(10))); Assert.False(await client.ExistsAsync("test")); } diff --git a/tests/Foundatio.Tests/Hosting/HostingTests.cs b/tests/Foundatio.Tests/Hosting/HostingTests.cs index b5eb71e46..1ce456a83 100644 --- a/tests/Foundatio.Tests/Hosting/HostingTests.cs +++ b/tests/Foundatio.Tests/Hosting/HostingTests.cs @@ -10,7 +10,6 @@ using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; using Xunit; using Xunit.Abstractions; diff --git a/tests/Foundatio.Tests/Jobs/JobTests.cs b/tests/Foundatio.Tests/Jobs/JobTests.cs index 55e353798..76d2d6e9d 100644 --- a/tests/Foundatio.Tests/Jobs/JobTests.cs +++ b/tests/Foundatio.Tests/Jobs/JobTests.cs @@ -6,11 +6,10 @@ using System.Threading.Tasks; using Foundatio.Caching; using Foundatio.Jobs; -using Foundatio.Metrics; -using Foundatio.Utility; using Foundatio.Xunit; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Time.Testing; using Xunit; using Xunit.Abstractions; @@ -23,18 +22,18 @@ public JobTests(ITestOutputHelper output) : base(output) { } [Fact] public async Task CanCancelJob() { - var job = new HelloWorldJob(Log); + var job = new HelloWorldJob(null, Log); var sp = new ServiceCollection().BuildServiceProvider(); var timeoutCancellationTokenSource = new CancellationTokenSource(1000); var resultTask = new JobRunner(job, sp, Log).RunAsync(timeoutCancellationTokenSource.Token); - await SystemClock.SleepAsync(TimeSpan.FromSeconds(2)); + await TimeProvider.System.Delay(TimeSpan.FromSeconds(2)); Assert.True(await resultTask); } [Fact] public async Task CanStopLongRunningJob() { - var job = new LongRunningJob(Log); + var job = new LongRunningJob(null, Log); var sp = new ServiceCollection().BuildServiceProvider(); var runner = new JobRunner(job, sp, Log); var cts = new CancellationTokenSource(1000); @@ -46,7 +45,7 @@ public async Task CanStopLongRunningJob() [Fact] public async Task CanStopLongRunningCronJob() { - var job = new LongRunningJob(Log); + var job = new LongRunningJob(null, Log); var sp = new ServiceCollection().BuildServiceProvider(); var runner = new JobRunner(job, sp, Log); var cts = new CancellationTokenSource(1000); @@ -58,7 +57,7 @@ public async Task CanStopLongRunningCronJob() [Fact] public async Task CanRunJobs() { - var job = new HelloWorldJob(Log); + var job = new HelloWorldJob(null, Log); Assert.Equal(0, job.RunCount); await job.RunAsync(); Assert.Equal(1, job.RunCount); @@ -74,7 +73,7 @@ public async Task CanRunJobs() sw.Stop(); Assert.InRange(sw.Elapsed, TimeSpan.FromMilliseconds(95), TimeSpan.FromMilliseconds(800)); - var jobInstance = new HelloWorldJob(Log); + var jobInstance = new HelloWorldJob(null, Log); Assert.NotNull(jobInstance); Assert.Equal(0, jobInstance.RunCount); Assert.Equal(JobResult.Success, await jobInstance.RunAsync()); @@ -84,7 +83,7 @@ public async Task CanRunJobs() [Fact] public async Task CanRunMultipleInstances() { - var job = new HelloWorldJob(Log); + var job = new HelloWorldJob(null, Log); var sp = new ServiceCollection().BuildServiceProvider(); HelloWorldJob.GlobalRunCount = 0; @@ -107,20 +106,18 @@ public async Task CanRunMultipleInstances() [Fact] public async Task CanCancelContinuousJobs() { - using (TestSystemClock.Install()) - { - var job = new HelloWorldJob(Log); - var sp = new ServiceCollection().BuildServiceProvider(); - var timeoutCancellationTokenSource = new CancellationTokenSource(100); - await job.RunContinuousAsync(TimeSpan.FromSeconds(1), 5, timeoutCancellationTokenSource.Token); + var timeProvider = new FakeTimeProvider { AutoAdvanceAmount = TimeSpan.FromSeconds(1)}; + var job = new HelloWorldJob(timeProvider, Log); + var sp = new ServiceCollection().AddSingleton(_ => timeProvider).BuildServiceProvider(); + var timeoutCancellationTokenSource = new CancellationTokenSource(100); + await job.RunContinuousAsync(TimeSpan.FromSeconds(1), 5, timeoutCancellationTokenSource.Token); - Assert.Equal(1, job.RunCount); + Assert.Equal(1, job.RunCount); - timeoutCancellationTokenSource = new CancellationTokenSource(500); - var runnerTask = new JobRunner(job, sp, Log, instanceCount: 5, iterationLimit: 10000, interval: TimeSpan.FromMilliseconds(1)).RunAsync(timeoutCancellationTokenSource.Token); - await SystemClock.SleepAsync(TimeSpan.FromSeconds(1)); - await runnerTask; - } + timeoutCancellationTokenSource = new CancellationTokenSource(TimeSpan.FromMilliseconds(50), timeProvider); + var runnerTask = new JobRunner(job, sp, Log, instanceCount: 5, iterationLimit: 10000, interval: TimeSpan.FromMilliseconds(1)).RunAsync(timeoutCancellationTokenSource.Token); + timeProvider.Advance(TimeSpan.FromSeconds(1)); + await runnerTask; } [Fact] @@ -157,41 +154,39 @@ public async Task CanRunThrottledJobs() [Fact] public async Task CanRunJobsWithInterval() { - using (TestSystemClock.Install()) - { - var time = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); - - TestSystemClock.SetFrozenTime(time); - TestSystemClock.UseFakeSleep(); + var time = new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero); + var timeProvider = new FakeTimeProvider(time); + var interval = TimeSpan.FromHours(.75); - var job = new HelloWorldJob(Log); - var interval = TimeSpan.FromHours(.75); + var job = new HelloWorldJob(timeProvider, Log); - await job.RunContinuousAsync(iterationLimit: 2, interval: interval); + var jobTask = Task.Run(() => job.RunContinuousAsync(iterationLimit: 2, interval: interval)); + while (job.RunCount < 1) + await Task.Delay(10); + timeProvider.Advance(interval); + await jobTask; - Assert.Equal(2, job.RunCount); - Assert.Equal(interval, (SystemClock.UtcNow - time)); - } + Assert.Equal(2, job.RunCount); + Assert.Equal(interval, (timeProvider.GetUtcNow() - time)); } [Fact] public async Task CanRunJobsWithIntervalBetweenFailingJob() { - using (TestSystemClock.Install()) - { - var time = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + var time = new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero); + var interval = TimeSpan.FromHours(.75); + var timeProvider = new FakeTimeProvider(time) { AutoAdvanceAmount = interval }; - TestSystemClock.SetFrozenTime(time); - TestSystemClock.UseFakeSleep(); + var job = new FailingJob(timeProvider, Log); - var job = new FailingJob(Log); - var interval = TimeSpan.FromHours(.75); + var jobTask = Task.Run(() => job.RunContinuousAsync(iterationLimit: 2, interval: interval)); + while (job.RunCount < 1) + await Task.Delay(10); + timeProvider.Advance(interval); + await jobTask; - await job.RunContinuousAsync(iterationLimit: 2, interval: interval); - - Assert.Equal(2, job.RunCount); - Assert.Equal(interval, (SystemClock.UtcNow - time)); - } + Assert.Equal(2, job.RunCount); + Assert.Equal(interval, (timeProvider.GetUtcNow() - time)); } [Fact(Skip = "Meant to be run manually.")] @@ -199,15 +194,9 @@ public async Task JobLoopPerf() { const int iterations = 10000; - var metrics = new InMemoryMetricsClient(new InMemoryMetricsClientOptions { LoggerFactory = Log }); - var job = new SampleJob(metrics, Log); + var job = new SampleJob(null, Log); var sw = Stopwatch.StartNew(); await job.RunContinuousAsync(null, iterations); sw.Stop(); - await metrics.FlushAsync(); - _logger.LogTrace((await metrics.GetCounterStatsAsync("runs")).ToString()); - _logger.LogTrace((await metrics.GetCounterStatsAsync("errors")).ToString()); - _logger.LogTrace((await metrics.GetCounterStatsAsync("failed")).ToString()); - _logger.LogTrace((await metrics.GetCounterStatsAsync("completed")).ToString()); } } diff --git a/tests/Foundatio.Tests/Jobs/WorkItemJobTests.cs b/tests/Foundatio.Tests/Jobs/WorkItemJobTests.cs index 307386c1d..9f9776108 100644 --- a/tests/Foundatio.Tests/Jobs/WorkItemJobTests.cs +++ b/tests/Foundatio.Tests/Jobs/WorkItemJobTests.cs @@ -9,10 +9,8 @@ using Foundatio.AsyncEx; using Foundatio.Jobs; using Foundatio.Messaging; -using Foundatio.Metrics; using Foundatio.Queues; using Foundatio.Tests.Extensions; -using Foundatio.Utility; using Foundatio.Xunit; using Microsoft.Extensions.Logging; using Xunit; @@ -39,7 +37,7 @@ public async Task CanRunWorkItem() for (int i = 0; i < 10; i++) { - await SystemClock.SleepAsync(100); + await Task.Delay(100); await ctx.ReportProgressAsync(10 * i); } }); @@ -68,11 +66,7 @@ public async Task CanHandleMultipleWorkItemInstances() { const int workItemCount = 1000; - using var metrics = new InMemoryMetricsClient(o => o.LoggerFactory(Log)); using var queue = new InMemoryQueue(o => o.RetryDelay(TimeSpan.Zero).Retries(0).LoggerFactory(Log)); -#pragma warning disable CS0618 // Type or member is obsolete - queue.AttachBehavior(new MetricsQueueBehavior(metrics, loggerFactory: Log)); -#pragma warning restore CS0618 // Type or member is obsolete using var messageBus = new InMemoryMessageBus(o => o.LoggerFactory(Log)); var handlerRegistry = new WorkItemHandlers(); var j1 = new WorkItemJob(queue, messageBus, handlerRegistry, Log); @@ -148,7 +142,7 @@ await messageBus.SubscribeAsync(status => _logger.LogError(ex, "One or more tasks were cancelled: {Message}", ex.Message); } - await SystemClock.SleepAsync(100); + await Task.Delay(100); if (_logger.IsEnabled(LogLevel.Information)) _logger.LogInformation("Completed: {CompletedItems} Errors: {Errors}", completedItems.Count, errors); Assert.Equal(workItemCount, completedItems.Count + errors); @@ -199,7 +193,7 @@ public async Task CanRunWorkItemWithDelegateHandler() for (int i = 1; i < 10; i++) { - await SystemClock.SleepAsync(100); + await Task.Delay(100); await ctx.ReportProgressAsync(10 * i); } }, Log.CreateLogger("MyWorkItem")); @@ -350,7 +344,7 @@ public override async Task HandleItemAsync(WorkItemContext context) for (int i = 1; i < 10; i++) { - await SystemClock.SleepAsync(10); + await Task.Delay(10); await context.ReportProgressAsync(10 * i); } } diff --git a/tests/Foundatio.Tests/Locks/InMemoryLockTests.cs b/tests/Foundatio.Tests/Locks/InMemoryLockTests.cs index f5535f446..19a5def2b 100644 --- a/tests/Foundatio.Tests/Locks/InMemoryLockTests.cs +++ b/tests/Foundatio.Tests/Locks/InMemoryLockTests.cs @@ -21,12 +21,12 @@ public InMemoryLockTests(ITestOutputHelper output) : base(output) protected override ILockProvider GetThrottlingLockProvider(int maxHits, TimeSpan period) { - return new ThrottlingLockProvider(_cache, maxHits, period, Log); + return new ThrottlingLockProvider(_cache, maxHits, period, null, Log); } protected override ILockProvider GetLockProvider() { - return new CacheLockProvider(_cache, _messageBus, Log); + return new CacheLockProvider(_cache, _messageBus, null, Log); } [Fact] diff --git a/tests/Foundatio.Tests/Metrics/DiagnosticsMetricsTests.cs b/tests/Foundatio.Tests/Metrics/DiagnosticsMetricsTests.cs deleted file mode 100644 index fd6db79dc..000000000 --- a/tests/Foundatio.Tests/Metrics/DiagnosticsMetricsTests.cs +++ /dev/null @@ -1,111 +0,0 @@ -using System; -using System.Linq; -using System.Threading; -using System.Threading.Tasks; -using Foundatio.Metrics; -using Foundatio.Xunit; -using Xunit; -using Xunit.Abstractions; - -namespace Foundatio.Tests.Metrics; - -public class DiagnosticsMetricsTests : TestWithLoggingBase, IDisposable -{ - private readonly DiagnosticsMetricsClient _client; - - public DiagnosticsMetricsTests(ITestOutputHelper output) : base(output) - { - _client = new DiagnosticsMetricsClient(o => o.MeterName("Test")); - } - - [Fact] - public void Counter() - { - using var metricsCollector = new DiagnosticsMetricsCollector("Test", _logger); - - _client.Counter("counter"); - - Assert.Single(metricsCollector.GetMeasurements()); - Assert.Equal("counter", metricsCollector.GetMeasurements().Single().Name); - Assert.Equal(1, metricsCollector.GetMeasurements().Single().Value); - } - - [Fact] - public void CounterWithValue() - { - using var metricsCollector = new DiagnosticsMetricsCollector("Test", _logger); - - _client.Counter("counter", 5); - _client.Counter("counter", 3); - - Assert.Equal(2, metricsCollector.GetMeasurements().Count); - Assert.All(metricsCollector.GetMeasurements(), m => - { - Assert.Equal("counter", m.Name); - }); - Assert.Equal(8, metricsCollector.GetSum("counter")); - Assert.Equal(2, metricsCollector.GetCount("counter")); - } - - [Fact] - public void Gauge() - { - using var metricsCollector = new DiagnosticsMetricsCollector("Test", _logger); - - _client.Gauge("gauge", 1.1); - - metricsCollector.RecordObservableInstruments(); - - Assert.Single(metricsCollector.GetMeasurements()); ; - Assert.Equal("gauge", metricsCollector.GetMeasurements().Single().Name); - Assert.Equal(1.1, metricsCollector.GetMeasurements().Single().Value); - } - - [Fact] - public void Timer() - { - using var metricsCollector = new DiagnosticsMetricsCollector("Test", _logger); - - _client.Timer("timer", 450); - _client.Timer("timer", 220); - - Assert.Equal(670, metricsCollector.GetSum("timer")); - Assert.Equal(2, metricsCollector.GetCount("timer")); - } - - [Fact] - public async Task CanWaitForCounter() - { - using var metricsCollector = new DiagnosticsMetricsCollector("Test", _logger); - - var success = await metricsCollector.WaitForCounterAsync("timer", () => - { - _client.Counter("timer", 1); - _client.Counter("timer", 2); - return Task.CompletedTask; - }, 3); - - Assert.True(success); - } - - [Fact] - public async Task CanTimeoutWaitingForCounter() - { - using var metricsCollector = new DiagnosticsMetricsCollector("Test", _logger); - - var success = await metricsCollector.WaitForCounterAsync("timer", () => - { - _client.Counter("timer", 1); - _client.Counter("timer", 2); - return Task.CompletedTask; - }, 4, new CancellationTokenSource(TimeSpan.FromSeconds(1)).Token); - - Assert.False(success); - } - - public void Dispose() - { - _client.Dispose(); - GC.SuppressFinalize(this); - } -} diff --git a/tests/Foundatio.Tests/Metrics/InMemoryMetricsTests.cs b/tests/Foundatio.Tests/Metrics/InMemoryMetricsTests.cs deleted file mode 100644 index 8196f0eeb..000000000 --- a/tests/Foundatio.Tests/Metrics/InMemoryMetricsTests.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System.Threading.Tasks; -using Foundatio.Metrics; -using Foundatio.Utility; - -using Xunit; -using Xunit.Abstractions; - -namespace Foundatio.Tests.Metrics; - -public class InMemoryMetricsTests : MetricsClientTestBase -{ - public InMemoryMetricsTests(ITestOutputHelper output) : base(output) { } - -#pragma warning disable CS0618 // Type or member is obsolete - public override IMetricsClient GetMetricsClient(bool buffered = false) -#pragma warning restore CS0618 // Type or member is obsolete - { - return new InMemoryMetricsClient(o => o.LoggerFactory(Log).Buffered(buffered)); - } - - [Fact] - public override Task CanSetGaugesAsync() - { - return base.CanSetGaugesAsync(); - } - - [Fact] - public override Task CanIncrementCounterAsync() - { - return base.CanIncrementCounterAsync(); - } - - [Fact] - public override Task CanWaitForCounterAsync() - { - using (TestSystemClock.Install()) - { - return base.CanWaitForCounterAsync(); - } - } - - [Fact] - public override Task CanGetBufferedQueueMetricsAsync() - { - return base.CanGetBufferedQueueMetricsAsync(); - } - - [Fact] - public override Task CanIncrementBufferedCounterAsync() - { - return base.CanIncrementBufferedCounterAsync(); - } - - [Fact] - public override Task CanSendBufferedMetricsAsync() - { - return base.CanSendBufferedMetricsAsync(); - } -} diff --git a/tests/Foundatio.Tests/Queue/InMemoryQueueTests.cs b/tests/Foundatio.Tests/Queue/InMemoryQueueTests.cs index 21bdb1d4a..4ee2d2855 100644 --- a/tests/Foundatio.Tests/Queue/InMemoryQueueTests.cs +++ b/tests/Foundatio.Tests/Queue/InMemoryQueueTests.cs @@ -4,7 +4,6 @@ using System.Threading; using System.Threading.Tasks; using Foundatio.Queues; -using Foundatio.Utility; using Microsoft.Extensions.Logging; using Xunit; using Xunit.Abstractions; @@ -17,7 +16,7 @@ public class InMemoryQueueTests : QueueTestBase public InMemoryQueueTests(ITestOutputHelper output) : base(output) { } - protected override IQueue GetQueue(int retries = 1, TimeSpan? workItemTimeout = null, TimeSpan? retryDelay = null, int[] retryMultipliers = null, int deadLetterMaxItems = 100, bool runQueueMaintenance = true) + protected override IQueue GetQueue(int retries = 1, TimeSpan? workItemTimeout = null, TimeSpan? retryDelay = null, int[] retryMultipliers = null, int deadLetterMaxItems = 100, bool runQueueMaintenance = true, TimeProvider timeProvider = null) { if (_queue == null) _queue = new InMemoryQueue(o => o @@ -25,6 +24,7 @@ protected override IQueue GetQueue(int retries = 1, TimeSpan? wo .Retries(retries) .RetryMultipliers(retryMultipliers ?? new[] { 1, 3, 5, 10 }) .WorkItemTimeout(workItemTimeout.GetValueOrDefault(TimeSpan.FromMinutes(5))) + .TimeProvider(timeProvider) .LoggerFactory(Log)); if (_logger.IsEnabled(LogLevel.Debug)) _logger.LogDebug("Queue Id: {QueueId}", _queue.QueueId); @@ -55,22 +55,22 @@ public async Task TestAsyncEvents() { disposables.Add(q.Enqueuing.AddHandler(async (sender, args) => { - await SystemClock.SleepAsync(250); + await Task.Delay(250); _logger.LogInformation("First Enqueuing"); })); disposables.Add(q.Enqueuing.AddHandler(async (sender, args) => { - await SystemClock.SleepAsync(250); + await Task.Delay(250); _logger.LogInformation("Second Enqueuing"); })); disposables.Add(q.Enqueued.AddHandler(async (sender, args) => { - await SystemClock.SleepAsync(250); + await Task.Delay(250); _logger.LogInformation("First"); })); disposables.Add(q.Enqueued.AddHandler(async (sender, args) => { - await SystemClock.SleepAsync(250); + await Task.Delay(250); _logger.LogInformation("Second"); })); @@ -204,10 +204,7 @@ public override Task CanHandleErrorInWorkerAsync() [Fact] public override Task WorkItemsWillTimeoutAsync() { - using (TestSystemClock.Install()) - { - return base.WorkItemsWillTimeoutAsync(); - } + return base.WorkItemsWillTimeoutAsync(); } [Fact] diff --git a/tests/Foundatio.Tests/Utility/ScheduledTimerTests.cs b/tests/Foundatio.Tests/Utility/ScheduledTimerTests.cs index e8fb1a805..47e404ff4 100644 --- a/tests/Foundatio.Tests/Utility/ScheduledTimerTests.cs +++ b/tests/Foundatio.Tests/Utility/ScheduledTimerTests.cs @@ -53,7 +53,7 @@ private async Task CanRunConcurrentlyAsync(TimeSpan? minimumIntervalTime = null) async Task Callback() { _logger.LogInformation("Starting work"); - await SystemClock.SleepAsync(250); + await Task.Delay(250); countdown.Signal(); _logger.LogInformation("Finished work"); return null; @@ -65,7 +65,7 @@ private async Task CanRunConcurrentlyAsync(TimeSpan? minimumIntervalTime = null) { for (int i = 0; i < iterations; i++) { - await SystemClock.SleepAsync(10); + await Task.Delay(10); timer.ScheduleNext(); } }); diff --git a/tests/Foundatio.Tests/Utility/SystemClockTests.cs b/tests/Foundatio.Tests/Utility/SystemClockTests.cs deleted file mode 100644 index dd14b6563..000000000 --- a/tests/Foundatio.Tests/Utility/SystemClockTests.cs +++ /dev/null @@ -1,130 +0,0 @@ -using System; -using System.Diagnostics; -using System.Threading.Tasks; -using Foundatio.Utility; -using Foundatio.Xunit; -using Xunit; -using Xunit.Abstractions; - -namespace Foundatio.Tests.Utility; - -public class SystemClockTests : TestWithLoggingBase -{ - public SystemClockTests(ITestOutputHelper output) : base(output) { } - - [Fact] - public void CanGetTime() - { - using (TestSystemClock.Install()) - { - var now = DateTime.UtcNow; - TestSystemClock.SetFrozenTime(now); - Assert.Equal(now, SystemClock.UtcNow); - Assert.Equal(now.ToLocalTime(), SystemClock.Now); - Assert.Equal(now, SystemClock.OffsetUtcNow); - Assert.Equal(now.ToLocalTime(), SystemClock.OffsetNow); - Assert.Equal(DateTimeOffset.Now.Offset, SystemClock.TimeZoneOffset); - } - } - - [Fact] - public void CanSleep() - { - using (TestSystemClock.Install()) - { - var sw = Stopwatch.StartNew(); - SystemClock.Sleep(250); - sw.Stop(); - Assert.InRange(sw.ElapsedMilliseconds, 225, 400); - - TestSystemClock.UseFakeSleep(); - - var now = SystemClock.UtcNow; - sw.Restart(); - SystemClock.Sleep(1000); - sw.Stop(); - var afterSleepNow = SystemClock.UtcNow; - - Assert.InRange(sw.ElapsedMilliseconds, 0, 30); - Assert.True(afterSleepNow > now); - Assert.InRange(afterSleepNow.Subtract(now).TotalMilliseconds, 950, 1100); - } - } - - [Fact] - public async Task CanSleepAsync() - { - using (TestSystemClock.Install()) - { - var sw = Stopwatch.StartNew(); - await SystemClock.SleepAsync(250); - sw.Stop(); - - Assert.InRange(sw.ElapsedMilliseconds, 225, 3000); - - TestSystemClock.UseFakeSleep(); - - var now = SystemClock.UtcNow; - sw.Restart(); - await SystemClock.SleepAsync(1000); - sw.Stop(); - var afterSleepNow = SystemClock.UtcNow; - - Assert.InRange(sw.ElapsedMilliseconds, 0, 30); - Assert.True(afterSleepNow > now); - Assert.InRange(afterSleepNow.Subtract(now).TotalMilliseconds, 950, 5000); - } - } - - [Fact] - public void CanSetTimeZone() - { - using (TestSystemClock.Install()) - { - var utcNow = DateTime.UtcNow; - var now = new DateTime(utcNow.AddHours(1).Ticks, DateTimeKind.Local); - TestSystemClock.SetFrozenTime(utcNow); - TestSystemClock.SetTimeZoneOffset(TimeSpan.FromHours(1)); - - Assert.Equal(utcNow, SystemClock.UtcNow); - Assert.Equal(utcNow, SystemClock.OffsetUtcNow); - Assert.Equal(now, SystemClock.Now); - Assert.Equal(new DateTimeOffset(now.Ticks, TimeSpan.FromHours(1)), SystemClock.OffsetNow); - Assert.Equal(TimeSpan.FromHours(1), SystemClock.TimeZoneOffset); - } - } - - [Fact] - public void CanSetLocalFixedTime() - { - using (TestSystemClock.Install()) - { - var now = DateTime.Now; - var utcNow = now.ToUniversalTime(); - TestSystemClock.SetFrozenTime(now); - - Assert.Equal(now, SystemClock.Now); - Assert.Equal(now, SystemClock.OffsetNow); - Assert.Equal(utcNow, SystemClock.UtcNow); - Assert.Equal(utcNow, SystemClock.OffsetUtcNow); - Assert.Equal(DateTimeOffset.Now.Offset, SystemClock.TimeZoneOffset); - } - } - - [Fact] - public void CanSetUtcFixedTime() - { - using (TestSystemClock.Install()) - { - var utcNow = DateTime.UtcNow; - var now = utcNow.ToLocalTime(); - TestSystemClock.SetFrozenTime(utcNow); - - Assert.Equal(now, SystemClock.Now); - Assert.Equal(now, SystemClock.OffsetNow); - Assert.Equal(utcNow, SystemClock.UtcNow); - Assert.Equal(utcNow, SystemClock.OffsetUtcNow); - Assert.Equal(DateTimeOffset.Now.Offset, SystemClock.TimeZoneOffset); - } - } -}