| /** |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with this |
| * work for additional information regarding copyright ownership. The ASF |
| * licenses this file to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations under |
| * the License. |
| */ |
| package org.apache.bookkeeper.stats.codahale; |
| |
| import static org.junit.Assert.assertEquals; |
| |
| import com.codahale.metrics.Snapshot; |
| |
| import java.util.ArrayList; |
| import java.util.Random; |
| import java.util.concurrent.TimeUnit; |
| import java.util.concurrent.atomic.AtomicInteger; |
| |
| import org.junit.Test; |
| |
| /** |
| * Unit tests for FastTimer. |
| * |
| */ |
| public class FastTimerTest { |
| |
| /* |
| * To simplify testing, we're over-riding the time source used by FastTimer with some |
| * fake time we're incrementing manually. This speeds-up testing (we don't have to wait |
| * for real seconds to elapse) and also guarantees deterministic behavior for the unit |
| * test. |
| */ |
| private static AtomicInteger mockedTime = new AtomicInteger(0); |
| |
| private int incSec() { |
| return mockedTime.incrementAndGet(); |
| } |
| |
| private FastTimer getMockedFastTimer(int timeWindowSeconds, FastTimer.Buckets buckets) { |
| return new FastTimer(timeWindowSeconds, buckets) { |
| @Override |
| protected int getTime() { |
| return mockedTime.get(); |
| } |
| }; |
| } |
| |
| @Test |
| public void testBuckets() { |
| FastTimer t = new FastTimer(1, FastTimer.Buckets.fine); |
| for (int b = 0; b < t.getNumberOfBuckets(); b++) { |
| long lowerBound = b > 0 ? t.getBucketBound(b - 1) + 1 : 0; |
| long bucketMean = t.getBucketValue(b); |
| long upperBound = t.getBucketBound(b); |
| System.out.println(String.format("Bucket %3d [%12d - %12d], avg=%12d", |
| b, lowerBound, upperBound, bucketMean)); |
| assertEquals(String.format("bucket for lowerBound value %d", lowerBound), |
| b, t.getBucket(lowerBound)); |
| assertEquals(String.format("bucket for bucketMean value %d", bucketMean), |
| b, t.getBucket(bucketMean)); |
| assertEquals(String.format("bucket for upperBound value %d", upperBound), |
| b, t.getBucket(upperBound)); |
| if (b > 0) { |
| assertEquals(String.format("bucket before bucket %d", b), b - 1, t.getBucket(lowerBound - 1)); |
| } |
| if (b + 1 < t.getNumberOfBuckets()) { |
| assertEquals(String.format("bucket after bucket %d", b), b + 1, t.getBucket(upperBound + 1)); |
| } |
| } |
| } |
| |
| @Test |
| public void testFunctional() { |
| FastTimer t = getMockedFastTimer(1, FastTimer.Buckets.fine); |
| for (int i = 0; i <= 10000; i++) { |
| t.update(i, TimeUnit.MICROSECONDS); |
| } |
| incSec(); // advance mocked time to next second |
| Snapshot s = t.getSnapshot(); |
| assertEquals("FastTimer.getCount()", 10001, t.getCount()); |
| assertEquals("FastSnapshot.getMin()", 1, s.getMin()); |
| assertEquals("FastSnapshot.getMax()", TimeUnit.MICROSECONDS.toNanos(10000), s.getMax()); |
| assertEquals("FastSnapshot.getMean()", TimeUnit.MICROSECONDS.toNanos(5000), (long) s.getMean()); |
| assertEquals("FastSnapshot.getMedian()", TimeUnit.MICROSECONDS.toNanos(5000), (long) s.getMedian()); |
| assertEquals("FastSnapshot.getValue(0.1)", TimeUnit.MICROSECONDS.toNanos(1000), (long) s.getValue(0.1)); |
| assertEquals("FastSnapshot.getValue(0.9)", TimeUnit.MICROSECONDS.toNanos(9000), (long) s.getValue(0.9)); |
| assertEquals("FastSnapshot.getValue(0.99)", TimeUnit.MICROSECONDS.toNanos(9900), (long) s.getValue(0.99)); |
| } |
| |
| @Test |
| public void testTimer() { |
| // load definitions for testing the timer |
| // following 3 array lengths must match: each element defines values for one phase |
| final int[] timeRange = new int[] { 90, 190, 50, 90, 100, 100 }; |
| final int[] timeBase = new int[] { 10, 10, 50, 10, 0, 0 }; |
| final int[] rate = new int[] { 1000, 1000, 1000, 1000, 0, 1 }; |
| |
| final int window = 5; // use a 5 second window for testing |
| FastTimer t = getMockedFastTimer(window, FastTimer.Buckets.fine); |
| Random r = new Random(12345); // fixed random seed for deterministic value distribution |
| int phase = 0; |
| int sec = 0; |
| |
| long count = 0; |
| // start generating test load for each of the configured phases |
| while (phase < timeRange.length) { |
| for (int i = 0; i < rate[phase]; i++) { |
| t.update(r.nextInt(timeRange[phase]) + timeBase[phase], TimeUnit.MILLISECONDS); |
| count++; |
| } |
| incSec(); // advance mocked time to next second |
| if (++sec % window == 0) { |
| // every WINDOW seconds, check the timer values |
| Snapshot s = t.getSnapshot(); |
| System.out.println(String.format( |
| "phase %3d: count=%10d, rate=%6.0f, min=%6.1f, avg=%6.1f, q99=%6.1f, max=%6.1f", |
| phase, t.getCount(), t.getMeanRate(), ((double) s.getMin()) / 1000000.0, |
| s.getMean() / 1000000.0, s.getValue(0.99) / 1000000.0, ((double) s.getMax()) / 1000000.0)); |
| |
| // check count (events the timer has ever seen) |
| assertEquals("FastTimer.getCount()", count, t.getCount()); |
| // check rate (should be precisely the configured rate) |
| assertEquals("FastTimer.getMeanRate()", rate[phase], |
| (int) Math.round(t.getMeanRate())); |
| assertEquals("FastTimer.getOneMinuteRate()", rate[phase], |
| (int) Math.round(t.getOneMinuteRate())); |
| assertEquals("FastTimer.getFiveMinuteRate()", rate[phase], |
| (int) Math.round(t.getFiveMinuteRate())); |
| assertEquals("FastTimer.getFifteenMinuteRate()", rate[phase], |
| (int) Math.round(t.getFifteenMinuteRate())); |
| // at rates > 1000 (with fixed seed), we know that the following checks will be successful |
| if (t.getMeanRate() >= 1000) { |
| // check minimum value == lower bound |
| assertEquals("FastSnapshot.getMin()", timeBase[phase], s.getMin() / 1000000); |
| // check maximum value == upper bound |
| assertEquals("FastSnapshot.getMax()", timeBase[phase] + timeRange[phase] - 1, |
| (s.getMax() / 1000000)); |
| // check 99th percentile == upper bound |
| assertEquals("FastSnapshot.getValue(0.99)", |
| t.getBucketBound(t.getBucket( |
| TimeUnit.MILLISECONDS.toNanos(timeBase[phase] + timeRange[phase] - 1))), |
| (long) s.getValue(0.99)); |
| // check mean is within 10% of configured mean |
| assertEquals("FastSnapshot.getMean()", (timeBase[phase] + (timeRange[phase] / 2)) / 10, |
| (int) (Math.round(s.getMean() / 1000000) / 10)); |
| } |
| |
| // start next phase |
| phase++; |
| } |
| } |
| } |
| |
| @Test |
| public void testTimerMultiThreaded() { |
| final int window = 5; // use a 5 second window for testing |
| FastTimer t = getMockedFastTimer(window, FastTimer.Buckets.fine); |
| |
| // start 10 threads, which each update the timer 1000 times |
| ArrayList<Thread> threads = new ArrayList<Thread>(); |
| for (int i = 0; i < 10; i++) { |
| Thread thread = new Thread(() -> { |
| for (int j = 0; j < 1000; j++) { |
| t.update(10, TimeUnit.MILLISECONDS); |
| } |
| }); |
| threads.add(thread); |
| thread.start(); |
| } |
| // wait for 10 threads to finish |
| for (Thread thread : threads) { |
| try { |
| thread.join(); |
| } catch (InterruptedException e) { |
| // ignore |
| } |
| } |
| incSec(); // advance mocked time to next second |
| |
| assertEquals("FastTimer.getCount()", 10000, t.getCount()); |
| assertEquals("FastTimer.getMeanRate()", 2000, (int) Math.round(t.getMeanRate())); |
| |
| Snapshot s = t.getSnapshot(); |
| assertEquals("FastSnapshot.getMin()", 10, s.getMin() / 1000000); |
| assertEquals("FastSnapshot.getMax()", 10, (s.getMax() / 1000000)); |
| assertEquals("FastSnapshot.getValue(0.99)", 10, Math.round(s.getValue(0.99) / 1000000)); |
| assertEquals("FastSnapshot.getMean()", 10, (int) Math.round(s.getMean() / 1000000)); |
| } |
| |
| @Test |
| public void testTimerNoBuckets() { |
| final int window = 5; // use a 5 second window for testing |
| FastTimer t = getMockedFastTimer(window, FastTimer.Buckets.none); |
| |
| for (int i = 0; i < 1000; i++) { |
| t.update(10, TimeUnit.MILLISECONDS); |
| } |
| incSec(); // advance mocked time to next second |
| |
| assertEquals("FastTimer.getCount()", 1000, t.getCount()); |
| assertEquals("FastTimer.getMeanRate()", 200, (int) Math.round(t.getMeanRate())); |
| |
| Snapshot s = t.getSnapshot(); |
| assertEquals("FastSnapshot.getMin()", 10, s.getMin() / 1000000); |
| assertEquals("FastSnapshot.getMax()", 10, (s.getMax() / 1000000)); |
| assertEquals("FastSnapshot.getValue(0.99)", 0, Math.round(s.getValue(0.99) / 1000000)); |
| assertEquals("FastSnapshot.getMean()", 10, (int) Math.round(s.getMean() / 1000000)); |
| } |
| |
| @Test |
| public void testSnapshotOutOfSync() { |
| FastTimer t = getMockedFastTimer(1, FastTimer.Buckets.fine); |
| t.update(t.getBucketBound(0) - 1, TimeUnit.NANOSECONDS); // add value to 1st bucket |
| t.update(t.getBucketBound(1) - 1, TimeUnit.NANOSECONDS); // add value to 2nd bucket |
| t.update(t.getBucketBound(2) - 1, TimeUnit.NANOSECONDS); // add value to 3rd bucket |
| incSec(); // advance mocked time to next second |
| Snapshot s1 = t.getSnapshot(); |
| long[] buckets = new long[t.getNumberOfBuckets()]; |
| buckets[0] = 1; |
| buckets[1] = 1; |
| buckets[2] = 1; |
| Snapshot s2 = new FastSnapshot(t, |
| t.getBucketBound(0) - 1, |
| t.getBucketBound(2) - 1, |
| t.getBucketBound(0) + t.getBucketBound(1) + t.getBucketBound(2) + 3, |
| 4, // count (4) is out of sync with number of recorded events in buckets (3) |
| buckets); |
| assertEquals("FastSnapshot.getMin()", s1.getMin(), s2.getMin()); |
| assertEquals("FastSnapshot.getMax()", s1.getMax(), s2.getMax()); |
| assertEquals("FastSnapshot.getValue(0.95)", (long) s1.getValue(0.95), (long) s2.getValue(0.95)); |
| } |
| |
| } |