public void GetSnapshot_WithinCachePeriod_ReturnsPreviousSnapshot() { var clock = new ManualTestClock(); // Within the metrics, the last snapshot timestamp will probably be zero. // Let's start our clock with something far away from zero. clock.AddMilliseconds(new SystemClock().GetMillisecondTimestamp()); var metrics = new StandardCommandMetrics( GroupKey.Named("Test"), new TransientConfigurableValue<long>(10000), new TransientConfigurableValue<long>(1000), clock, new IgnoringStats()); metrics.MarkCommandSuccess(); metrics.GetSnapshot(); // Take the first snapshot to cache it. metrics.MarkCommandSuccess(); clock.AddMilliseconds(500); // Still within the snapshot TTL (1000). Assert.Equal(1, metrics.GetSnapshot().Total); clock.AddMilliseconds(1000); // Push time past the TTL. Assert.Equal(2, metrics.GetSnapshot().Total); }
public void Increment_AfterPeriodExceeded_ResetsBeforeIncrementing() { const long periodMillis = 1000; var clock = new ManualTestClock(); var bucket = new ResettingNumbersBucket(clock, new TransientConfigurableValue<long>(periodMillis)); bucket.Increment(CounterMetric.CommandSuccess); clock.AddMilliseconds(periodMillis + 1); Assert.Equal(1, bucket.GetCount(CounterMetric.CommandSuccess)); bucket.Increment(CounterMetric.CommandSuccess); // Should reset and then count one. Assert.Equal(1, bucket.GetCount(CounterMetric.CommandSuccess)); // Should be 1, not 2. }
public void Increment_AfterPeriodExceeded_ResetsBeforeIncrementing() { const long periodMillis = 1000; var clock = new ManualTestClock(); var bucket = new ResettingNumbersBucket(clock, new TransientConfigurableValue <long>(periodMillis)); bucket.Increment(CounterMetric.CommandSuccess); clock.AddMilliseconds(periodMillis + 1); Assert.Equal(1, bucket.GetCount(CounterMetric.CommandSuccess)); bucket.Increment(CounterMetric.CommandSuccess); // Should reset and then count one. Assert.Equal(1, bucket.GetCount(CounterMetric.CommandSuccess)); // Should be 1, not 2. }
public void IsAllowing_MultipleDurationsBetweenFailureAndNextFailure_KeepsBreakerTripped() { const long durationMillis = 10000; var clock = new ManualTestClock(); var mockMetrics = CreateMockMetricsWithSnapshot(10, 100); // 10 ops, 100% failing. var breaker = new BreakerBuilder(1, 1) // Trip at 1 op, 1% failing. .WithMockMetrics(mockMetrics) .WithWaitMillis(durationMillis) .WithClock(clock) .Create(); Assert.False(breaker.IsAllowing()); // Trip the breaker. clock.AddMilliseconds(durationMillis * 5); Assert.True(breaker.IsAllowing()); // Single test, but it won't MarkSuccess(). Assert.False(breaker.IsAllowing()); // Make sure the last one was the single test. clock.AddMilliseconds(durationMillis * 5); Assert.True(breaker.IsAllowing()); // Single test, but it won't MarkSuccess(). Assert.False(breaker.IsAllowing()); // Make sure the last one was the single test. }
public void GetSnapshot_WithinCachePeriod_ReturnsPreviousSnapshot() { var clock = new ManualTestClock(); var key = AnyGroupKey; var mockConfig = new Mock <IFailurePercentageCircuitBreakerConfig>(MockBehavior.Strict); mockConfig.Setup(m => m.GetWindowMillis(key)).Returns(10000); mockConfig.Setup(m => m.GetSnapshotTtlMillis(key)).Returns(1000); // Within the metrics, the last snapshot timestamp will probably be zero. // Let's start our clock with something far away from zero. clock.AddMilliseconds(new UtcSystemClock().GetMillisecondTimestamp()); var metrics = new StandardCommandMetrics(key, mockConfig.Object, clock, new DefaultMjolnirLogFactory()); metrics.MarkCommandSuccess(); metrics.GetSnapshot(); // Take the first snapshot to cache it. metrics.MarkCommandSuccess(); clock.AddMilliseconds(500); // Still within the snapshot TTL (1000). Assert.Equal(1, metrics.GetSnapshot().Total); clock.AddMilliseconds(1000); // Push time past the TTL. Assert.Equal(2, metrics.GetSnapshot().Total); }
public void IsAllowing_AfterTrippedAndWithinWaitPeriod_Rejects() { var clock = new ManualTestClock(); var mockMetrics = CreateMockMetricsWithSnapshot(10, 100); // 10 ops, 100% failing. var breaker = new BreakerBuilder(1, 1) // Trip at 1 op, 1% failing. .WithMockMetrics(mockMetrics) .WithWaitMillis(10000) .WithClock(clock) .Create(); Assert.False(breaker.IsAllowing()); // Should reject and trip. clock.AddMilliseconds(5000); // Half the wait duration. Assert.False(breaker.IsAllowing()); }
public void IsAllowing_AfterTrippedAndAfterWaitPeriod_SendsSingleTestAndRejectsOthers() { var clock = new ManualTestClock(); var mockMetrics = CreateMockMetricsWithSnapshot(10, 100); // 10 ops, 100% failing. var breaker = new BreakerBuilder(1, 1) // Trip at 1 op, 1% failing. .WithMockMetrics(mockMetrics) .WithWaitMillis(10000) .WithClock(clock) .Create(); Assert.False(breaker.IsAllowing()); // Should reject and trip. clock.AddMilliseconds(11000); Assert.True(breaker.IsAllowing()); // Allow the first one, it's the single test. Assert.False(breaker.IsAllowing()); // Reject the next one, the test was already allowed. }
public void GetSnapshot_NotCachedAndCached() { var mockStats = new Mock <IStats>(); var clock = new ManualTestClock(); var metrics = CreateMetrics("Test", mockStats, clock, 30000, 10000); clock.AddMilliseconds(10100); // Pass the snapshot TTL. metrics.GetSnapshot(); // Should create new snapshot. mockStats.Verify(m => m.Elapsed("mjolnir metrics Test CreateSnapshot", null, It.IsAny <TimeSpan>()), Times.Once); mockStats.Verify(m => m.Elapsed("mjolnir metrics Test GetSnapshot", null, It.IsAny <TimeSpan>()), Times.Once); metrics.GetSnapshot(); // Should grab the cached one without re-creating. mockStats.Verify(m => m.Elapsed("mjolnir metrics Test CreateSnapshot", null, It.IsAny <TimeSpan>()), Times.Once); // Still once. mockStats.Verify(m => m.Elapsed("mjolnir metrics Test GetSnapshot", null, It.IsAny <TimeSpan>()), Times.Exactly(2)); // One more time. }
public void AllowSingleTest_TrippedAndPastWaitDuration() { var mockStats = new Mock <IStats>(); var clock = new ManualTestClock(); var breaker = new BreakerBuilder(0, 0, "Test") .WithStats(mockStats.Object) .WithClock(clock) .WithWaitMillis(1000) .Create(); breaker.IsAllowing(); // Trip. mockStats.Verify(m => m.Elapsed("mjolnir breaker Test AllowSingleTest", "NotEligible", It.IsAny <TimeSpan>()), Times.Once); clock.AddMilliseconds(2000); // Advance past wait duration. breaker.IsAllowing(); mockStats.Verify(m => m.Elapsed("mjolnir breaker Test AllowSingleTest", "Allowed", It.IsAny <TimeSpan>()), Times.Once); }
public void MarkSuccess_WhenTrippedAndAfterWaitDuration_ResetsMetrics() { var clock = new ManualTestClock(); var mockMetrics = CreateMockMetricsWithSnapshot(1, 100); var breaker = new BreakerBuilder(1, 50) .WithMockMetrics(mockMetrics) .WithWaitMillis(10000) .WithClock(clock) .Create(); Assert.False(breaker.IsAllowing()); // Trip the breaker first. clock.AddMilliseconds(11000); Assert.True(breaker.IsAllowing()); // Single test... breaker.MarkSuccess(0); // ... that's successful. mockMetrics.Verify(m => m.Reset(), Times.Once); }
public void GetSnapshot_NotCachedAndCached() { var mockStats = new Mock<IStats>(); var clock = new ManualTestClock(); var metrics = CreateMetrics("Test", mockStats, clock, 30000, 10000); clock.AddMilliseconds(10100); // Pass the snapshot TTL. metrics.GetSnapshot(); // Should create new snapshot. mockStats.Verify(m => m.Elapsed("mjolnir metrics Test CreateSnapshot", null, It.IsAny<TimeSpan>()), Times.Once); mockStats.Verify(m => m.Elapsed("mjolnir metrics Test GetSnapshot", null, It.IsAny<TimeSpan>()), Times.Once); metrics.GetSnapshot(); // Should grab the cached one without re-creating. mockStats.Verify(m => m.Elapsed("mjolnir metrics Test CreateSnapshot", null, It.IsAny<TimeSpan>()), Times.Once); // Still once. mockStats.Verify(m => m.Elapsed("mjolnir metrics Test GetSnapshot", null, It.IsAny<TimeSpan>()), Times.Exactly(2)); // One more time. }
public void Increment_AfterPeriodExceeded_ResetsBeforeIncrementing() { const long periodMillis = 1000; var mockConfig = new Mock <IFailurePercentageCircuitBreakerConfig>(MockBehavior.Strict); mockConfig.Setup(m => m.GetWindowMillis(It.IsAny <GroupKey>())).Returns(periodMillis); var clock = new ManualTestClock(); var bucket = new ResettingNumbersBucket(AnyGroupKey, clock, mockConfig.Object, new DefaultMjolnirLogFactory()); bucket.Increment(CounterMetric.CommandSuccess); clock.AddMilliseconds(periodMillis + 1); Assert.Equal(1, bucket.GetCount(CounterMetric.CommandSuccess)); bucket.Increment(CounterMetric.CommandSuccess); // Should reset and then count one. Assert.Equal(1, bucket.GetCount(CounterMetric.CommandSuccess)); // Should be 1, not 2. }
public void IsAllowing_AfterFailedSingleTest_KeepsBreakerTrippedAndSendsAnotherTestAfterAnotherDuration() { var clock = new ManualTestClock(); var mockMetrics = CreateMockMetricsWithSnapshot(10, 100); // 10 ops, 100% failing. var breaker = new BreakerBuilder(1, 1) // Trip at 1 op, 1% failing. .WithMockMetrics(mockMetrics) .WithWaitMillis(10000) .WithClock(clock) .Create(); Assert.False(breaker.IsAllowing()); // Should reject and trip. clock.AddMilliseconds(11000); Assert.True(breaker.IsAllowing()); // Single test. // Advance to halfway through the duration. We should still be rejecting. clock.AddMilliseconds(6000); Assert.False(breaker.IsAllowing()); // No mark success call here. Metrics will probably have a failure added to it. clock.AddMilliseconds(5000); // 6000 + 5000 = 11000 > 10000 (the duration). Assert.True(breaker.IsAllowing()); // It's been another duration, we should allow another single test. // Advance to halfway through the duration. We should still be rejecting. clock.AddMilliseconds(6000); Assert.False(breaker.IsAllowing()); // No mark success call here. Metrics will probably have a failure added to it. clock.AddMilliseconds(5000); // 6000 + 5000 = 11000 > 10000 (the duration). Assert.True(breaker.IsAllowing()); // It's been another duration, we should allow another single test. // Advance a second or so, pretend the single test took some time. clock.AddMilliseconds(500); // Let's pretend this test succeeds. Mark it and reset the metrics. breaker.MarkSuccess(0); mockMetrics.Setup(m => m.GetSnapshot()).Returns(new MetricsSnapshot(0, 0)); // We should immediately be opened. Assert.True(breaker.IsAllowing()); Assert.True(breaker.IsAllowing()); Assert.True(breaker.IsAllowing()); }
public void IsAllowing_MultipleDurationsBetweenFailureAndNextSuccess_FixesBreakerOnSuccess() { const long durationMillis = 10000; var clock = new ManualTestClock(); var mockMetrics = CreateMockMetricsWithSnapshot(10, 100); // 10 ops, 100% failing. var breaker = new BreakerBuilder(1, 1) // Trip at 1 op, 1% failing. .WithMockMetrics(mockMetrics) .WithWaitMillis(durationMillis) .WithClock(clock) .Create(); Assert.False(breaker.IsAllowing()); // Trip the breaker. clock.AddMilliseconds(durationMillis * 5); Assert.True(breaker.IsAllowing()); // Single test. breaker.MarkSuccess(0); mockMetrics.Setup(m => m.GetSnapshot()).Returns(new MetricsSnapshot(0, 0)); Assert.True(breaker.IsAllowing()); Assert.True(breaker.IsAllowing()); Assert.True(breaker.IsAllowing()); }
public void IsAllowing_AfterSuccessfulSingleTest_FixesBreaker() { var clock = new ManualTestClock(); var mockMetrics = CreateMockMetricsWithSnapshot(10, 100); // 10 ops, 100% failing. var breaker = new BreakerBuilder(1, 1) // Trip at 1 op, 1% failing. .WithMockMetrics(mockMetrics) .WithWaitMillis(10000) .WithClock(clock) .Create(); Assert.False(breaker.IsAllowing()); // Should reject and trip. clock.AddMilliseconds(11000); breaker.IsAllowing(); // Send the single test. clock.AddMilliseconds(500); // Pretend the test operation took a bit (not really necessary here). breaker.MarkSuccess(0); // The single test transaction marks a success. // Metrics should be reset. mockMetrics.Verify(m => m.Reset(), Times.Once); mockMetrics.Setup(m => m.GetSnapshot()).Returns(new MetricsSnapshot(0, 0)); // Should be fixed now. // Test IsAllowing() twice to make sure it's not just the single test we're allowing. Assert.True(breaker.IsAllowing()); Assert.True(breaker.IsAllowing()); }
public void ManualTestClock_AddMilliseconds_DisallowsNegatives() { var clock = new ManualTestClock(); Assert.Throws <ArgumentException>(() => clock.AddMilliseconds(-10)); }
public void AllowSingleTest_TrippedAndPastWaitDuration() { var mockStats = new Mock<IStats>(); var clock = new ManualTestClock(); var breaker = new BreakerBuilder(0, 0, "Test") .WithStats(mockStats.Object) .WithClock(clock) .WithWaitMillis(1000) .Create(); breaker.IsAllowing(); // Trip. mockStats.Verify(m => m.Elapsed("mjolnir breaker Test AllowSingleTest", "NotEligible", It.IsAny<TimeSpan>()), Times.Once); clock.AddMilliseconds(2000); // Advance past wait duration. breaker.IsAllowing(); mockStats.Verify(m => m.Elapsed("mjolnir breaker Test AllowSingleTest", "Allowed", It.IsAny<TimeSpan>()), Times.Once); }
public void MarkSuccess_ForLongRunningSingleTest_FixesBreaker() { // Since we use the elapsed time in MarkSuccess(), address the following situation: // 1. Breaker trips // 2. Window passes // 3. Single test is allowed // 4. Single test takes a long time (say, > 2 windows) to complete (even though it started after we tripped). // Verify that: // - No other requests are allowed while the single test is waiting. // - The single test, if successful, fixes the breaker. // This is somewhat unlikely since we probably wont have command timeouts // that'd allow this. But worth verifying. var clock = new ManualTestClock(); var mockMetrics = CreateMockMetricsWithSnapshot(2, 100); // 2 ops, 100% failing. var breaker = new BreakerBuilder(1, 1) .WithMockMetrics(mockMetrics) .WithClock(clock) .Create(); // 1 ops, 1% failure required to break. var duration = breaker.Properties.TrippedDurationMillis; Assert.False(breaker.IsAllowing()); // Should immediately trip. clock.AddMilliseconds(duration.Value + 10); Assert.True(breaker.IsAllowing()); // Single test is allowed. Assert.False(breaker.IsAllowing()); clock.AddMilliseconds(duration.Value * 2); breaker.MarkSuccess(duration.Value * 2); // Metrics will have been reset. mockMetrics.Setup(m => m.GetSnapshot()).Returns(new MetricsSnapshot(0, 0)); Assert.True(breaker.IsAllowing()); Assert.True(breaker.IsAllowing()); }