Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

비동기적으로 작동하는 이벤트(Actor메시지)의 성능을 유닛테스트기를 활용하여 측정할수 있는 방법을 닷넷 환경에서 비동기적으로 작동하는 이벤트의 Message Delivery Once 를 검증하는방법과
Nbench를 이용하여 유닛테스트환경에서 성능측정을 할수 있는방법을 소개합니다.
이 샘플에서는 액터성능 테스트가 예시되어있지만 액터모델과 상관없이  유닛테스트내에서 심플한 측정모드를 액터메시지 모델이 이용되었지만 액터모델과 상관없이~
유닛테스트내에서  성능측정 기법을 이용할수 있습니다.

측정할 수 없는 것은 관리할수 없다 - 피터 드러크

Table of Contents


테스트 탐색기

...

테스트 탐색기를 통해 성능유닛 테스트를 수행하고 측정할수 있습니다.

...


기본 유닛테스트

...

Code Block
themeEmacs
        [Theory(DisplayName = "RoundRobinPoolTest")]
        [InlineData(3,1000)]
        public void RoundRobinPoolTest(int nodeCount, int testCount, bool isPerformTest = false)
        {
            var actorSystem = akkaService.GetActorSystem();

            TestProbe testProbe = this.CreateTestProbe(actorSystem);

            var props = new RoundRobinPool(nodeCount)                
                .Props(Props.Create(() => new BasicActor()));

            var actor = actorSystem.ActorOf(props);

            for (int i = 0; i < nodeCount; i++)
            {
                actor.Tell(testProbe.Ref);
            }

            int cutOff = 3000;

            Within(TimeSpan.FromMilliseconds(cutOff), () =>
            {
                for (int i = 0; i < testCount; i++)
                {
                    actor.Tell("hello" + i);
                }

                for (int i = 0; i < testCount; i++)
                {
                    testProbe.ExpectMsg("world");
                    
                    if (isPerformTest)
                    {
                        _dictionary.Add(_key++, _key);
                        _addCounter.Increment();
                    }
                }
            });
        }

...

유닛테스트가 완료된 로직의 성능테스트가 필요시 Nbench를 활용하여 성능 유닛테스트 검사기를 추가할수 있습니다.

Code Block
themeEmacs
        [NBenchFact]        
        [PerfBenchmark(NumberOfIterations = 3, RunMode = RunMode.Throughput,
        RunTimeMilliseconds = 1000, TestMode = TestMode.Test)]
        [CounterThroughputAssertion("TestCounter", MustBe.GreaterThan, 1000.0d)]
        [CounterTotalAssertion("TestCounter", MustBe.GreaterThan, 1500.0d)]
        [CounterMeasurement("TestCounter")]
        public void RoundRobinPoolTestPerformanceTest()
        {
            RoundRobinPoolTest(5, 3000, true);
        }

...

성능 유닛트세트가 작성되면, VS-IDE가 제공하는 테스트 탐색기를 통해서도 수행할수 있습니다.



성능측정 리포트

...

Code Block
themeEmacs
    [PASS] Expected [Counter] TestCounter to must be greater than 1,000.00 operations; actual value was 94,160.06 operations.
    
    [PASS] Expected [Counter] TestCounter to must be greater than 1,500.00 operations; actual value was 93,000.00 operations.
    
    
    ---------- Measurements ----------
    
    Metric : [Counter] TestCounter
    
    Per Second ( operations )
    Average         : 94160.06329051661
    Max             : 94937.13528317196
    Min             : 93266.78965179897
    Std. Deviation  : 841.2138544819605
    Std. Error      : 485.6750453312026
    
    Per Test ( operations )
    Average         : 93000
    Max             : 93000
    Min             : 93000
    Std. Deviation  : 0
    Std. Error      : 0
    
    ----------

...

  •  [PASS] Expected [Counter] TestCounter to must be greater than 1,000.00 operations; actual value was 94,160.06 operations.
    • CounterThroughputAssertion 에서 설정된 값 이상인 최소 TPS 통과됨을 의미하며 추가로 94160 수행되었음을 표현합니다.
  • [PASS] Expected [Counter] TestCounter to must be greater than 1,500.00 operations; actual value was 93,000.00 operations.
    • CounterTotalAssertion  에서 설정된 값 이상인 평균 TPS가 통과됨을 의미하며 평균 수행횟수가 93000임을 표현합니다.


성능제약 테스트

...

다양한 이유로 호출량을 통제해야할 필요도 있습니다.  최종 소비자가 충분한 소비를 하지못하는경우 생산을 제약하는 경우이며

성능제약이 잘 작동하는지도 검증할수 있습니다.


이벤트는 N개 동시 발생시킬수 있지만, TPS제약을 할수있는  ThrottleLimitActor 가 준비되었으며

Code Block
themeEmacs
[Theory(DisplayName = "테스트 n초당 1회 호출제약")]
[InlineData(5, 1, false)]
public void ThrottleLimitTest(int givenTestCount, int givenLimitSeconds, bool isPerformTest)
{
.......
  // Create ThrottleLimit Actor
  throttleLimitActor = actorSystem.ActorOf(Props.Create(() => new ThrottleLimitActor(1, givenLimitSeconds, 1000)));
  throttleLimitActor.Tell(new SetTarget(probe))
  
  for (int i = 0; i < givenTestCount; i++)
  {
      throttleLimitActor.Tell(new EventCmd()
      {
          Message = "test",
      });
  }
  
  //Then : Safe processing within N seconds limit
  for (int i = 0; i < givenTestCount; i++)
  {
      probe.ExpectMsg<EventCmd>(message =>
      {
          Assert.Equal("test", message.Message);                        
      });
  
      output.WriteLine($"[{DateTime.Now}] - GTPRequestCmd");
  
      if (isPerformTest)
      {
          _dictionary.Add(_key++, _key);
          _addCounter.Increment();
      }
  }
...........
}  

기본유닛테스트는 발생메시지가 모두 소비가 되었는가만을 검증합니다.


TPS 1을 유지하였는가? 는 다음과 같이 분리하여 측정될수 있습니다.

Code Block
themeEmacs
        [NBenchFact]
        [PerfBenchmark(NumberOfIterations = 3, RunMode = RunMode.Throughput,
        RunTimeMilliseconds = 1000, TestMode = TestMode.Test)]
        [CounterThroughputAssertion("TestCounter", MustBe.LessThanOrEqualTo, 1.0d)]
        [CounterTotalAssertion("TestCounter", MustBe.LessThanOrEqualTo, 1)]
        [CounterMeasurement("TestCounter")]
        public void ThrottleLimitPerformanceTest()
        {
            ThrottleLimitTest(1, 1, true);
        }


통과옵션을을  1보다 작다로 설정을 하여 TPS1이하를 검증을 할수가 있습니다.

  • [CounterThroughputAssertion("TestCounter", MustBe.LessThanOrEqualTo, 1.0d)]
  • [CounterTotalAssertion("TestCounter", MustBe.LessThanOrEqualTo, 1)]


성능제약 통과 로그

Code Block
themeEmacs
    [PASS] Expected [Counter] TestCounter to must be less than or equal to 1.00 operations; actual value was 0.98 operations.
    
    [PASS] Expected [Counter] TestCounter to must be less than or equal to 1.00 operations; actual value was 1.00 operations.
    
    
    ---------- Measurements ----------
    
    Metric : [Counter] TestCounter
    
    Per Second ( operations )
    Average         : 0.9802234345891607
    Max             : 0.9823810933292493
    Min             : 0.9787775577268321
    Std. Deviation  : 0.0019042957466220024
    Std. Error      : 0.0010994456619288725
    
    Per Test ( operations )
    Average         : 1
    Max             : 1
    Min             : 1
    Std. Deviation  : 0
    Std. Error      : 0
    


검증 옵션과 측정수가 일치하지 않으면 다음과같이 유닛테스트가 실패하게 됩니다.

(실패를 유발하기위해 GreaterThanOrEqualTo 으로 설정한 케이스)

Code Block
    [FAIL] Expected [Counter] TestCounter to must be greater than or equal to 1.00 operations; actual value was 0.98 operations.
    Expected: True
    Actual:   False

샘플 코드 : https://github.com/psmon/NetCoreLabs/blob/main/ActorLibTest/tools/ThrottleLimitActorTest.cs


Image Added

여기서 확장된 개념이 소비자의 소비능력을 측정하여 생산량을 동적 TPS조절할수 있는 조절기를 설계하는것이 BackPressure 입니다.


GC 성능 테스트기

...

Code Block
    ---------- Measurements ----------

Metric : TotalCollections [Gen0]

Per Second ( collections )
Average         : 251.73166139361632
Max             : 305.97882626522244
Min             : 178.82369771642138
Std. Deviation  : 47.515272144026895
Std. Error      : 15.025648361787713

Per Test ( collections )
Average         : 1
Max             : 1
Min             : 1
Std. Deviation  : 0
Std. Error      : 0

----------

Metric : TotalCollections [Gen1]

Per Second ( collections )
Average         : 251.73166139361632
Max             : 305.97882626522244
Min             : 178.82369771642138
Std. Deviation  : 47.515272144026895
Std. Error      : 15.025648361787713

Per Test ( collections )
Average         : 1
Max             : 1
Min             : 1
Std. Deviation  : 0
Std. Error      : 0

GC 성능테스트 측정도 이용할수 있는것은 보너스입니다.


이상  유닛테스트와 함께 심플한 성능테스트를 함께 할수 있는 방법을 살펴보았으며

측정할 수 없으면 성능 개선을 할수 없는것과 마찬가지로 여기서 이용된 기술과 관련기술 링크도 첨부합니다.


JUnit with Bench

자바에서는 JHM를 이용하여 마이크로 벤치마크할수 있으며 대응하는 버전도 동일하게 준비되어있습니다.

링크 : https://github.com/psmon/java-labs/blob/master/springweb/src/jmh/java/com/webnori/springweb/akka/README.md



참고링크 :