internal async Task RegisterMetrics_WhenServerReturns200Ok_ReturnsTrue( UnleashSettings settings, [Frozen] Mock <FakeHttpMessageHandler> httpMessageHandler, [Frozen] UnleashApiClientRequestHeaders requestHeaders, MetricsBucket metricsBucket ) { var jsonSerializerSettings = new NewtonsoftJsonSerializerSettings(); var jsonSerializer = new NewtonsoftJsonSerializer(jsonSerializerSettings); httpMessageHandler.SetupPostSendMetricsRequestForSuccess(requestHeaders); var httpClient = new HttpClient(httpMessageHandler.Object) { BaseAddress = settings.UnleashApi }; var apiClient = new UnleashApiClient(httpClient, jsonSerializer, requestHeaders); var threadSafeMetricsBucket = new ThreadSafeMetricsBucket(metricsBucket); var successResult = await apiClient.SendMetrics(threadSafeMetricsBucket, CancellationToken.None); Assert.True(successResult); httpMessageHandler.VerifyAll(); }
internal async Task RegisterMetrics_WhenHttpExceptionOccurs_ReturnsFalse( HttpStatusCode responseStatusCode, string responseBody, string responseContentType, UnleashSettings settings, [Frozen] Mock <FakeHttpMessageHandler> httpMessageHandler, [Frozen] UnleashApiClientRequestHeaders requestHeaders, MetricsBucket metricsBucket ) { var jsonSerializerSettings = new NewtonsoftJsonSerializerSettings(); var jsonSerializer = new NewtonsoftJsonSerializer(jsonSerializerSettings); httpMessageHandler.SetupPostSendMetricsRequestForException(requestHeaders, responseStatusCode, responseBody, responseContentType); var httpClient = new HttpClient(httpMessageHandler.Object) { BaseAddress = settings.UnleashApi }; var apiClient = new UnleashApiClient(httpClient, jsonSerializer, requestHeaders); var threadSafeMetricsBucket = new ThreadSafeMetricsBucket(metricsBucket); var successResult = await apiClient.SendMetrics(threadSafeMetricsBucket, CancellationToken.None); Assert.False(successResult); httpMessageHandler.VerifyAll(); }
internal async Task ExecuteAsync_WhenHttpExceptionOccurs_CompletesSuccessfully( HttpStatusCode statusCode, string responseBody, string responseContentType, [Frozen] UnleashSettings settings, [Frozen] Mock <FakeHttpMessageHandler> httpMessageHandler, [Frozen] Mock <IUnleashApiClientFactory> apiClientFactoryMock, [Frozen] UnleashApiClientRequestHeaders requestHeaders, [Frozen] MetricsBucket metricsBucket) { var jsonSerializerSettings = new NewtonsoftJsonSerializerSettings(); var jsonSerializer = new NewtonsoftJsonSerializer(jsonSerializerSettings); var threadSafeMetricsBucket = new ThreadSafeMetricsBucket(metricsBucket); var backgroundTask = new ClientMetricsBackgroundTask(apiClientFactoryMock.Object, settings, threadSafeMetricsBucket); httpMessageHandler.SetupPostSendMetricsRequestForException(requestHeaders, statusCode, responseBody, responseContentType); var httpClient = new HttpClient(httpMessageHandler.Object) { BaseAddress = settings.UnleashApi }; var apiClient = new UnleashApiClient(httpClient, jsonSerializer, requestHeaders); apiClientFactoryMock.Setup(cf => cf.CreateClient()).Returns(apiClient); await backgroundTask.ExecuteAsync(CancellationToken.None); httpMessageHandler.VerifyAll(); apiClientFactoryMock.VerifyAll(); }
public void RegisterCount_WhenInvokedFromMultipleThreadsAndCheckedIncrementally_BehavesPredictably(int maxDegreeOfParallelism, long totalRegistrations, int featureToggleCount, double breakPercent) { var metricsBucket = new ThreadSafeMetricsBucket(); var options = new ParallelOptions { MaxDegreeOfParallelism = maxDegreeOfParallelism }; var breaks = totalRegistrations * breakPercent; var toggleCounter = 0L; var recordedTasks = 0L; var exceptions = new ConcurrentBag <Exception>(); var result = Parallel.For(1, totalRegistrations + 1, options, state => { try { Interlocked.Increment(ref recordedTasks); var toggleName = (state % featureToggleCount).ToString(); var active = DateTime.Now.Ticks % 2 == 0; metricsBucket.RegisterCount(toggleName, active); if (state % breaks == 0) { using (metricsBucket.StopCollectingMetrics(out var bucket)) { var count = GetNumberOfRegistrations(bucket); Interlocked.Add(ref toggleCounter, count); _testOutputHelper.WriteLine($"Locking bucket at #{state} with {toggleCounter} toggle registrations"); } }
public void SingleBucket_OneCountPerTask3() { var metricsBucket = new ThreadSafeMetricsBucket(); var options = new ParallelOptions { MaxDegreeOfParallelism = 10, }; var totalTasks = 1000000L; var breaks = totalTasks / 10; long toggleCounter = 0; long recordedTasks = 0; var result = Parallel.For(1, totalTasks + 1, options, state => { Interlocked.Increment(ref recordedTasks); var toggleName = (state % 50).ToString(); var active = DateTime.Now.Ticks % 2 == 0; metricsBucket.RegisterCount(toggleName, active); if (state % breaks == 0) { using (metricsBucket.StopCollectingMetrics(out var bucket)) { var count = GetNumberOfRegistrations(bucket); Interlocked.Add(ref toggleCounter, count); Console.WriteLine($"Locking bucket at #{state} with {toggleCounter} toggle registrations"); } }
public void SingleBucket_No_WriteOperations() { var metricsBucket = new ThreadSafeMetricsBucket(); var options = new ParallelOptions { MaxDegreeOfParallelism = 50, }; var totalTasks = 1000000; var numFeatureToggles = 50; Parallel.For(0, totalTasks, options, state => { var i = (state % numFeatureToggles).ToString(); var predicate = DateTime.Now.Ticks % 2 == 0; metricsBucket.RegisterCount(i, predicate); }); using (metricsBucket.StopCollectingMetrics(out var bucket)) { bucket.Toggles.Count.Should().Be(numFeatureToggles); // Should be cleared var count = GetNumberOfRegistrations(bucket); count.Should().Be(totalTasks); } }
public UnleashServices(UnleashSettings settings, IRandom random, IUnleashApiClientFactory unleashApiClientFactory, IUnleashScheduledTaskManager scheduledTaskManager, IToggleCollectionCache toggleCollectionCache, IEnumerable <IStrategy> strategies) { this.Random = random ?? throw new ArgumentNullException(nameof(random)); this.scheduledTaskManager = scheduledTaskManager ?? throw new ArgumentNullException(nameof(scheduledTaskManager)); var settingsValidator = new UnleashSettingsValidator(); settingsValidator.Validate(settings); StrategyMap = BuildStrategyMap(strategies?.ToArray() ?? new IStrategy[0]); CancellationToken = cancellationTokenSource.Token; var cachedFilesResult = toggleCollectionCache.Load(cancellationTokenSource.Token).GetAwaiter().GetResult(); cacheMiss = cachedFilesResult.IsCacheMiss; ToggleCollection = new ThreadSafeToggleCollection { Instance = cachedFilesResult.InitialToggleCollection ?? new ToggleCollection() }; MetricsBucket = new ThreadSafeMetricsBucket(); var scheduledTasks = CreateScheduledTasks(settings, unleashApiClientFactory, toggleCollectionCache, cachedFilesResult); scheduledTaskManager.Configure(scheduledTasks, CancellationToken); }
public ClientMetricsBackgroundTask( IUnleashApiClient apiClient, UnleashSettings settings, ThreadSafeMetricsBucket metricsBucket) { this.apiClient = apiClient; this.settings = settings; this.metricsBucket = metricsBucket; }
public async Task SendMetrics_WhenInvoked_CompletesSuccessfully() { var metricsBucket = new ThreadSafeMetricsBucket(); metricsBucket.RegisterCount("Demo123", true); metricsBucket.RegisterCount("Demo123", false); var result = await Client.SendMetrics(metricsBucket, CancellationToken.None); Assert.True(result); // Check result: // http://unleash.herokuapp.com/#/features/view/Demo123 // http://unleash.herokuapp.com/api/admin/metrics/feature-toggles }
public async Task SendMetrics_Success() { var metricsBucket = new ThreadSafeMetricsBucket(); metricsBucket.RegisterCount("Demo123", true); metricsBucket.RegisterCount("Demo123", false); var result = await api.SendMetrics(metricsBucket, CancellationToken.None); result.Should().Be(true); // Check result: // http://unleash.herokuapp.com/#/features/view/Demo123 // http://unleash.herokuapp.com/api/admin/metrics/feature-toggles }
public async Task <bool> SendMetrics(ThreadSafeMetricsBucket metrics, CancellationToken cancellationToken) { const string requestUri = "api/client/metrics"; var memoryStream = new MemoryStream(); using (metrics.StopCollectingMetrics(out var bucket)) { jsonSerializer.Serialize(memoryStream, new ClientMetrics { AppName = clientRequestHeaders.AppName, InstanceId = clientRequestHeaders.InstanceTag, Bucket = bucket }); } return(await Post(requestUri, memoryStream, cancellationToken).ConfigureAwait(false)); }
public void RegisterCount_WhenInvokedFromMultipleThreadsAndCheckedAtCompletion_BehavesPredictably(int maxDegreeOfParallelism, int totalRegistrations, int featureToggleCount) { var metricsBucket = new ThreadSafeMetricsBucket(); var options = new ParallelOptions { MaxDegreeOfParallelism = maxDegreeOfParallelism }; var exceptions = new ConcurrentBag <Exception>(); var result = Parallel.For(0, totalRegistrations, options, state => { try { var toggleName = (state % featureToggleCount).ToString(); var active = DateTime.Now.Ticks % 2 == 0; metricsBucket.RegisterCount(toggleName, active); } catch (Exception exc) { exceptions.Add(exc); } }); if (exceptions.Count > 0) { throw new AggregateException(exceptions); } Assert.True(result.IsCompleted); using (metricsBucket.StopCollectingMetrics(out var bucket)) { Assert.Equal(featureToggleCount, bucket.Toggles.Count); var actualRegistrations = GetNumberOfRegistrations(bucket); Assert.Equal(totalRegistrations, actualRegistrations); } }
public Task <bool> SendMetrics(ThreadSafeMetricsBucket metricsBucket, CancellationToken cancellationToken) { return(Task.FromResult(true)); }
public UnleashServices(UnleashSettings settings, Dictionary <string, IStrategy> strategyMap) { var fileSystem = settings.FileSystem ?? new FileSystem(settings.Encoding); var backupFile = settings.GetFeatureToggleFilePath(); var etagBackupFile = settings.GetFeatureToggleETagFilePath(); // Cancellation CancellationToken = cancellationTokenSource.Token; ContextProvider = settings.UnleashContextProvider; var loader = new CachedFilesLoader(settings.JsonSerializer, fileSystem, backupFile, etagBackupFile); var cachedFilesResult = loader.EnsureExistsAndLoad(); ToggleCollection = new ThreadSafeToggleCollection { Instance = cachedFilesResult.InitialToggleCollection ?? new ToggleCollection() }; MetricsBucket = new ThreadSafeMetricsBucket(); IUnleashApiClient apiClient; if (settings.UnleashApiClient == null) { var httpClient = settings.HttpClientFactory.Create(settings.UnleashApi); apiClient = new UnleashApiClient(httpClient, settings.JsonSerializer, new UnleashApiClientRequestHeaders() { AppName = settings.AppName, InstanceTag = settings.InstanceTag, CustomHttpHeaders = settings.CustomHttpHeaders }); if (settings.LoadTogglesImmediately) { var toggles = apiClient.FetchToggles("", CancellationToken.None); ToggleCollection.Instance = toggles.Result.ToggleCollection; } } else { // Mocked backend: fill instance collection apiClient = settings.UnleashApiClient; var toggles = apiClient.FetchToggles("", CancellationToken.None); ToggleCollection.Instance = toggles.Result.ToggleCollection; } scheduledTaskManager = settings.ScheduledTaskManager; IsMetricsDisabled = settings.SendMetricsInterval == null; var fetchFeatureTogglesTask = new FetchFeatureTogglesTask( apiClient, ToggleCollection, settings.JsonSerializer, fileSystem, backupFile, etagBackupFile) { ExecuteDuringStartup = true, Interval = settings.FetchTogglesInterval, Etag = cachedFilesResult.InitialETag }; var scheduledTasks = new List <IUnleashScheduledTask>() { fetchFeatureTogglesTask }; if (settings.SendMetricsInterval != null) { var clientRegistrationBackgroundTask = new ClientRegistrationBackgroundTask( apiClient, settings, strategyMap.Select(pair => pair.Key).ToList()) { Interval = TimeSpan.Zero, ExecuteDuringStartup = true }; scheduledTasks.Add(clientRegistrationBackgroundTask); var clientMetricsBackgroundTask = new ClientMetricsBackgroundTask( apiClient, settings, MetricsBucket) { ExecuteDuringStartup = false, Interval = settings.SendMetricsInterval.Value }; scheduledTasks.Add(clientMetricsBackgroundTask); } scheduledTaskManager.Configure(scheduledTasks, CancellationToken); }