View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.hadoop.hbase.regionserver;
19  
20  import static org.junit.Assert.assertEquals;
21  import static org.junit.Assert.assertFalse;
22  import static org.junit.Assert.assertTrue;
23  
24  import java.io.IOException;
25  
26  import org.apache.hadoop.hbase.io.TimeRange;
27  import org.apache.hadoop.hbase.testclassification.SmallTests;
28  import org.apache.hadoop.hbase.util.Writables;
29  import org.junit.Test;
30  import org.junit.experimental.categories.Category;
31  import java.util.concurrent.ThreadLocalRandom;
32  
33  @Category({SmallTests.class})
34  public class TestTimeRangeTracker {
35    private static final int NUM_KEYS = 10000000;
36  
37    @Test
38    public void testExtreme() {
39      TimeRange tr = new TimeRange();
40      assertTrue(tr.includesTimeRange(new TimeRange()));
41      TimeRangeTracker trt = new TimeRangeTracker();
42      assertFalse(trt.includesTimeRange(new TimeRange()));
43      trt.includeTimestamp(1);
44      trt.includeTimestamp(10);
45      assertTrue(trt.includesTimeRange(new TimeRange()));
46    }
47  
48    @Test
49    public void testTimeRangeInitialized() {
50      TimeRangeTracker src = new TimeRangeTracker();
51      TimeRange tr = new TimeRange(System.currentTimeMillis());
52      assertFalse(src.includesTimeRange(tr));
53    }
54  
55    @Test
56    public void testTimeRangeTrackerNullIsSameAsTimeRangeNull() throws IOException {
57      TimeRangeTracker src = new TimeRangeTracker(1, 2);
58      byte [] bytes = Writables.getBytes(src);
59      TimeRange tgt = TimeRangeTracker.getTimeRange(bytes);
60      assertEquals(src.getMin(), tgt.getMin());
61      assertEquals(src.getMax(), tgt.getMax());
62    }
63  
64    @Test
65    public void testSerialization() throws IOException {
66      TimeRangeTracker src = new TimeRangeTracker(1, 2);
67      TimeRangeTracker tgt = new TimeRangeTracker();
68      Writables.copyWritable(src, tgt);
69      assertEquals(src.getMin(), tgt.getMin());
70      assertEquals(src.getMax(), tgt.getMax());
71    }
72  
73    @Test
74    public void testAlwaysDecrementingSetsMaximum() {
75      TimeRangeTracker trr = new TimeRangeTracker();
76      trr.includeTimestamp(3);
77      trr.includeTimestamp(2);
78      trr.includeTimestamp(1);
79      assertTrue(trr.getMin() != TimeRangeTracker.INITIAL_MIN_TIMESTAMP);
80      assertTrue(trr.getMax() != -1 /*The initial max value*/);
81    }
82  
83    @Test
84    public void testSimpleInRange() {
85      TimeRangeTracker trr = new TimeRangeTracker();
86      trr.includeTimestamp(0);
87      trr.includeTimestamp(2);
88      assertTrue(trr.includesTimeRange(new TimeRange(1)));
89    }
90  
91    /**
92     * Run a bunch of threads against a single TimeRangeTracker and ensure we arrive
93     * at right range.  Here we do ten threads each incrementing over 100k at an offset
94     * of the thread index; max is 10 * 10k and min is 0.
95     * @throws InterruptedException
96     */
97    @Test
98    public void testArriveAtRightAnswer() throws InterruptedException {
99      final TimeRangeTracker trr = new TimeRangeTracker();
100     final int threadCount = 10;
101     final int calls = 1000 * 1000;
102     Thread [] threads = new Thread[threadCount];
103     for (int i = 0; i < threads.length; i++) {
104       Thread t = new Thread("" + i) {
105         @Override
106         public void run() {
107           int offset = Integer.parseInt(getName());
108           boolean even = offset % 2 == 0;
109           if (even) {
110             for (int i = (offset * calls); i < calls; i++) trr.includeTimestamp(i);
111           } else {
112             int base = offset * calls;
113             for (int i = base + calls; i >= base; i--) trr.includeTimestamp(i);
114           }
115         }
116       };
117       t.start();
118       threads[i] = t;
119     }
120     for (int i = 0; i < threads.length; i++) {
121       threads[i].join();
122     }
123 
124     assertTrue(trr.getMax() == calls * threadCount);
125     assertTrue(trr.getMin() == 0);
126   }
127 
128   @Test
129   public void testRangeConstruction() throws IOException {
130     TimeRange defaultRange = new TimeRange();
131     assertEquals(0L, defaultRange.getMin());
132     assertEquals(Long.MAX_VALUE, defaultRange.getMax());
133     assertTrue(defaultRange.isAllTime());
134 
135     TimeRange oneArgRange = new TimeRange(0L);
136     assertEquals(0L, oneArgRange.getMin());
137     assertEquals(Long.MAX_VALUE, oneArgRange.getMax());
138     assertTrue(oneArgRange.isAllTime());
139 
140     TimeRange oneArgRange2 = new TimeRange(1);
141     assertEquals(1, oneArgRange2.getMin());
142     assertEquals(Long.MAX_VALUE, oneArgRange2.getMax());
143     assertFalse(oneArgRange2.isAllTime());
144 
145     TimeRange twoArgRange = new TimeRange(0L, Long.MAX_VALUE);
146     assertEquals(0L, twoArgRange.getMin());
147     assertEquals(Long.MAX_VALUE, twoArgRange.getMax());
148     assertTrue(twoArgRange.isAllTime());
149 
150     TimeRange twoArgRange2 = new TimeRange(0L, Long.MAX_VALUE - 1);
151     assertEquals(0L, twoArgRange2.getMin());
152     assertEquals(Long.MAX_VALUE - 1, twoArgRange2.getMax());
153     assertFalse(twoArgRange2.isAllTime());
154 
155     TimeRange twoArgRange3 = new TimeRange(1, Long.MAX_VALUE);
156     assertEquals(1, twoArgRange3.getMin());
157     assertEquals(Long.MAX_VALUE, twoArgRange3.getMax());
158     assertFalse(twoArgRange3.isAllTime());
159   }
160 
161   final static int NUM_OF_THREADS = 20;
162 
163   class RandomTestData {
164     private long[] keys = new long[NUM_KEYS];
165     private long min = Long.MAX_VALUE;
166     private long max = 0;
167 
168     public RandomTestData() {
169       if (ThreadLocalRandom.current().nextInt(NUM_OF_THREADS) % 2 == 0) {
170         for (int i = 0; i < NUM_KEYS; i++) {
171           keys[i] = i + ThreadLocalRandom.current().nextLong(NUM_OF_THREADS);
172           if (keys[i] < min) min = keys[i];
173           if (keys[i] > max) max = keys[i];
174         }
175       } else {
176         for (int i = NUM_KEYS - 1; i >= 0; i--) {
177           keys[i] = i + ThreadLocalRandom.current().nextLong(NUM_OF_THREADS);
178           if (keys[i] < min) min = keys[i];
179           if (keys[i] > max) max = keys[i];
180         }
181       }
182     }
183 
184     public long getMax() {
185       return this.max;
186     }
187 
188     public long getMin() {
189       return this.min;
190     }
191   }
192 
193   class TrtUpdateRunnable implements Runnable {
194 
195     private TimeRangeTracker trt;
196     private RandomTestData data;
197     public TrtUpdateRunnable(final TimeRangeTracker trt, final RandomTestData data) {
198       this.trt = trt;
199       this.data = data;
200     }
201 
202     public void run() {
203       for (long key : data.keys) {
204         trt.includeTimestamp(key);
205       }
206     }
207   }
208 
209   /**
210    * Run a bunch of threads against a single TimeRangeTracker and ensure we arrive
211    * at right range.  The data chosen is going to ensure that there are lots collisions, i.e,
212    * some other threads may already update the value while one tries to update min/max value.
213    */
214   @Test
215   public void testConcurrentIncludeTimestampCorrectness() {
216     RandomTestData[] testData = new RandomTestData[NUM_OF_THREADS];
217     long min = Long.MAX_VALUE, max = 0;
218     for (int i = 0; i < NUM_OF_THREADS; i ++) {
219       testData[i] = new RandomTestData();
220       if (testData[i].getMin() < min) {
221         min = testData[i].getMin();
222       }
223       if (testData[i].getMax() > max) {
224         max = testData[i].getMax();
225       }
226     }
227 
228     TimeRangeTracker trt = new TimeRangeTracker();
229 
230     Thread[] t = new Thread[NUM_OF_THREADS];
231     for (int i = 0; i < NUM_OF_THREADS; i++) {
232       t[i] = new Thread(new TrtUpdateRunnable(trt, testData[i]));
233       t[i].start();
234     }
235 
236     for (Thread thread : t) {
237       try {
238         thread.join();
239       } catch (InterruptedException e) {
240         e.printStackTrace();
241       }
242     }
243 
244     assertTrue(min == trt.getMin());
245     assertTrue(max == trt.getMax());
246   }
247 
248   /**
249    * Bit of code to test concurrent access on this class.
250    * @param args
251    * @throws InterruptedException
252    */
253   public static void main(String[] args) throws InterruptedException {
254     long start = System.currentTimeMillis();
255     final TimeRangeTracker trr = new TimeRangeTracker();
256     final int threadCount = 5;
257     final int calls = 1024 * 1024 * 128;
258     Thread [] threads = new Thread[threadCount];
259     for (int i = 0; i < threads.length; i++) {
260       Thread t = new Thread("" + i) {
261         @Override
262         public void run() {
263           for (int i = 0; i < calls; i++) trr.includeTimestamp(i);
264         }
265       };
266       t.start();
267       threads[i] = t;
268     }
269     for (int i = 0; i < threads.length; i++) {
270       threads[i].join();
271     }
272     System.out.println(trr.getMin() + " " + trr.getMax() + " " +
273       (System.currentTimeMillis() - start));
274   }
275 }