001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase;
019
020import static org.junit.Assert.assertEquals;
021import static org.junit.Assert.assertFalse;
022import static org.junit.Assert.assertNotNull;
023import static org.junit.Assert.assertTrue;
024import static org.junit.Assert.fail;
025
026import com.codahale.metrics.Histogram;
027import com.codahale.metrics.Snapshot;
028import com.codahale.metrics.UniformReservoir;
029import java.io.BufferedReader;
030import java.io.ByteArrayInputStream;
031import java.io.IOException;
032import java.io.InputStreamReader;
033import java.lang.reflect.Constructor;
034import java.lang.reflect.InvocationTargetException;
035import java.nio.charset.StandardCharsets;
036import java.util.LinkedList;
037import java.util.NoSuchElementException;
038import java.util.Queue;
039import java.util.Random;
040import java.util.concurrent.ThreadLocalRandom;
041import org.apache.hadoop.fs.FSDataInputStream;
042import org.apache.hadoop.fs.FileSystem;
043import org.apache.hadoop.fs.Path;
044import org.apache.hadoop.hbase.PerformanceEvaluation.RandomReadTest;
045import org.apache.hadoop.hbase.PerformanceEvaluation.TestOptions;
046import org.apache.hadoop.hbase.regionserver.CompactingMemStore;
047import org.apache.hadoop.hbase.testclassification.MiscTests;
048import org.apache.hadoop.hbase.testclassification.SmallTests;
049import org.apache.hadoop.hbase.util.GsonUtil;
050import org.junit.ClassRule;
051import org.junit.Test;
052import org.junit.experimental.categories.Category;
053
054import org.apache.hbase.thirdparty.com.google.gson.Gson;
055
056@Category({ MiscTests.class, SmallTests.class })
057public class TestPerformanceEvaluation {
058  @ClassRule
059  public static final HBaseClassTestRule CLASS_RULE =
060    HBaseClassTestRule.forClass(TestPerformanceEvaluation.class);
061
062  private static final HBaseTestingUtility HTU = new HBaseTestingUtility();
063
064  @Test
065  public void testDefaultInMemoryCompaction() {
066    PerformanceEvaluation.TestOptions defaultOpts = new PerformanceEvaluation.TestOptions();
067    assertEquals(CompactingMemStore.COMPACTING_MEMSTORE_TYPE_DEFAULT,
068      defaultOpts.getInMemoryCompaction().toString());
069    HTableDescriptor htd = PerformanceEvaluation.getTableDescriptor(defaultOpts);
070    for (HColumnDescriptor hcd : htd.getFamilies()) {
071      assertEquals(CompactingMemStore.COMPACTING_MEMSTORE_TYPE_DEFAULT,
072        hcd.getInMemoryCompaction().toString());
073    }
074  }
075
076  @Test
077  public void testSerialization() {
078    PerformanceEvaluation.TestOptions options = new PerformanceEvaluation.TestOptions();
079    assertFalse(options.isAutoFlush());
080    options.setAutoFlush(true);
081    Gson gson = GsonUtil.createGson().create();
082    String optionsString = gson.toJson(options);
083    PerformanceEvaluation.TestOptions optionsDeserialized =
084      gson.fromJson(optionsString, PerformanceEvaluation.TestOptions.class);
085    assertTrue(optionsDeserialized.isAutoFlush());
086  }
087
088  /**
089   * Exercise the mr spec writing. Simple assertions to make sure it is basically working.
090   */
091  @Test
092  public void testWriteInputFile() throws IOException {
093    TestOptions opts = new PerformanceEvaluation.TestOptions();
094    final int clients = 10;
095    opts.setNumClientThreads(clients);
096    opts.setPerClientRunRows(10);
097    Path dir =
098      PerformanceEvaluation.writeInputFile(HTU.getConfiguration(), opts, HTU.getDataTestDir());
099    FileSystem fs = FileSystem.get(HTU.getConfiguration());
100    Path p = new Path(dir, PerformanceEvaluation.JOB_INPUT_FILENAME);
101    long len = fs.getFileStatus(p).getLen();
102    assertTrue(len > 0);
103    byte[] content = new byte[(int) len];
104    try (FSDataInputStream dis = fs.open(p)) {
105      dis.readFully(content);
106      BufferedReader br = new BufferedReader(
107        new InputStreamReader(new ByteArrayInputStream(content), StandardCharsets.UTF_8));
108      int count = 0;
109      while (br.readLine() != null) {
110        count++;
111      }
112      assertEquals(clients, count);
113    }
114  }
115
116  @Test
117  public void testSizeCalculation() {
118    TestOptions opts = new PerformanceEvaluation.TestOptions();
119    opts = PerformanceEvaluation.calculateRowsAndSize(opts);
120    int rows = opts.getPerClientRunRows();
121    // Default row count
122    final int defaultPerClientRunRows = 1024 * 1024;
123    assertEquals(defaultPerClientRunRows, rows);
124    // If size is 2G, then twice the row count.
125    opts.setSize(2.0f);
126    opts = PerformanceEvaluation.calculateRowsAndSize(opts);
127    assertEquals(defaultPerClientRunRows * 2, opts.getPerClientRunRows());
128    // If two clients, then they get half the rows each.
129    opts.setNumClientThreads(2);
130    opts = PerformanceEvaluation.calculateRowsAndSize(opts);
131    assertEquals(defaultPerClientRunRows, opts.getPerClientRunRows());
132    // What if valueSize is 'random'? Then half of the valueSize so twice the rows.
133    opts.valueRandom = true;
134    opts = PerformanceEvaluation.calculateRowsAndSize(opts);
135    assertEquals(defaultPerClientRunRows * 2, opts.getPerClientRunRows());
136  }
137
138  @Test
139  public void testRandomReadCalculation() {
140    TestOptions opts = new PerformanceEvaluation.TestOptions();
141    opts = PerformanceEvaluation.calculateRowsAndSize(opts);
142    int rows = opts.getPerClientRunRows();
143    // Default row count
144    final int defaultPerClientRunRows = 1024 * 1024;
145    assertEquals(defaultPerClientRunRows, rows);
146    // If size is 2G, then twice the row count.
147    opts.setSize(2.0f);
148    opts.setPerClientRunRows(1000);
149    opts.setCmdName(PerformanceEvaluation.RANDOM_READ);
150    opts = PerformanceEvaluation.calculateRowsAndSize(opts);
151    assertEquals(1000, opts.getPerClientRunRows());
152    // If two clients, then they get half the rows each.
153    opts.setNumClientThreads(2);
154    opts = PerformanceEvaluation.calculateRowsAndSize(opts);
155    assertEquals(1000, opts.getPerClientRunRows());
156    // assuming we will get one before this loop expires
157    boolean foundValue = false;
158    Random rand = ThreadLocalRandom.current();
159    for (int i = 0; i < 10000000; i++) {
160      int randomRow = PerformanceEvaluation.generateRandomRow(rand, opts.totalRows);
161      if (randomRow > 1000) {
162        foundValue = true;
163        break;
164      }
165    }
166    assertTrue("We need to get a value more than 1000", foundValue);
167  }
168
169  @Test
170  public void testZipfian() throws NoSuchMethodException, SecurityException, InstantiationException,
171    IllegalAccessException, IllegalArgumentException, InvocationTargetException {
172    TestOptions opts = new PerformanceEvaluation.TestOptions();
173    opts.setValueZipf(true);
174    final int valueSize = 1024;
175    opts.setValueSize(valueSize);
176    RandomReadTest rrt = new RandomReadTest(null, opts, null);
177    Constructor<?> ctor =
178      Histogram.class.getDeclaredConstructor(com.codahale.metrics.Reservoir.class);
179    ctor.setAccessible(true);
180    Histogram histogram = (Histogram) ctor.newInstance(new UniformReservoir(1024 * 500));
181    for (int i = 0; i < 100; i++) {
182      histogram.update(rrt.getValueLength(null));
183    }
184    Snapshot snapshot = histogram.getSnapshot();
185    double stddev = snapshot.getStdDev();
186    assertTrue(stddev != 0 && stddev != 1.0);
187    assertTrue(snapshot.getStdDev() != 0);
188    double median = snapshot.getMedian();
189    assertTrue(median != 0 && median != 1 && median != valueSize);
190  }
191
192  @Test
193  public void testSetBufferSizeOption() {
194    TestOptions opts = new PerformanceEvaluation.TestOptions();
195    long bufferSize = opts.getBufferSize();
196    assertEquals(bufferSize, 2L * 1024L * 1024L);
197    opts.setBufferSize(64L * 1024L);
198    bufferSize = opts.getBufferSize();
199    assertEquals(bufferSize, 64L * 1024L);
200  }
201
202  @Test
203  public void testParseOptsWithThreads() {
204    Queue<String> opts = new LinkedList<>();
205    String cmdName = "sequentialWrite";
206    int threads = 1;
207    opts.offer(cmdName);
208    opts.offer(String.valueOf(threads));
209    PerformanceEvaluation.TestOptions options = PerformanceEvaluation.parseOpts(opts);
210    assertNotNull(options);
211    assertNotNull(options.getCmdName());
212    assertEquals(cmdName, options.getCmdName());
213    assertEquals(threads, options.getNumClientThreads());
214  }
215
216  @Test
217  public void testParseOptsWrongThreads() {
218    Queue<String> opts = new LinkedList<>();
219    String cmdName = "sequentialWrite";
220    opts.offer(cmdName);
221    opts.offer("qq");
222    try {
223      PerformanceEvaluation.parseOpts(opts);
224    } catch (IllegalArgumentException e) {
225      System.out.println(e.getMessage());
226      assertEquals("Command " + cmdName + " does not have threads number", e.getMessage());
227      assertTrue(e.getCause() instanceof NumberFormatException);
228    }
229  }
230
231  @Test
232  public void testParseOptsNoThreads() {
233    Queue<String> opts = new LinkedList<>();
234    String cmdName = "sequentialWrite";
235    try {
236      PerformanceEvaluation.parseOpts(opts);
237    } catch (IllegalArgumentException e) {
238      System.out.println(e.getMessage());
239      assertEquals("Command " + cmdName + " does not have threads number", e.getMessage());
240      assertTrue(e.getCause() instanceof NoSuchElementException);
241    }
242  }
243
244  @Test
245  public void testParseOptsMultiPuts() {
246    Queue<String> opts = new LinkedList<>();
247    String cmdName = "sequentialWrite";
248    opts.offer("--multiPut=10");
249    opts.offer(cmdName);
250    opts.offer("64");
251    PerformanceEvaluation.TestOptions options = null;
252    try {
253      options = PerformanceEvaluation.parseOpts(opts);
254      fail("should fail");
255    } catch (IllegalArgumentException e) {
256      System.out.println(e.getMessage());
257    }
258
259    // Re-create options
260    opts = new LinkedList<>();
261    opts.offer("--autoFlush=true");
262    opts.offer("--multiPut=10");
263    opts.offer(cmdName);
264    opts.offer("64");
265
266    options = PerformanceEvaluation.parseOpts(opts);
267    assertNotNull(options);
268    assertNotNull(options.getCmdName());
269    assertEquals(cmdName, options.getCmdName());
270    assertEquals(10, options.getMultiPut());
271  }
272
273  @Test
274  public void testParseOptsMultiPutsAndAutoFlushOrder() {
275    Queue<String> opts = new LinkedList<>();
276    String cmdName = "sequentialWrite";
277    String cmdMultiPut = "--multiPut=10";
278    String cmdAutoFlush = "--autoFlush=true";
279    opts.offer(cmdAutoFlush);
280    opts.offer(cmdMultiPut);
281    opts.offer(cmdName);
282    opts.offer("64");
283    PerformanceEvaluation.TestOptions options = null;
284    options = PerformanceEvaluation.parseOpts(opts);
285    assertNotNull(options);
286    assertEquals(true, options.autoFlush);
287    assertEquals(10, options.getMultiPut());
288
289    // Change the order of AutoFlush and Multiput
290    opts = new LinkedList<>();
291    opts.offer(cmdMultiPut);
292    opts.offer(cmdAutoFlush);
293    opts.offer(cmdName);
294    opts.offer("64");
295
296    options = null;
297    options = PerformanceEvaluation.parseOpts(opts);
298    assertNotNull(options);
299    assertEquals(10, options.getMultiPut());
300    assertEquals(true, options.autoFlush);
301  }
302
303  @Test
304  public void testParseOptsConnCount() {
305    Queue<String> opts = new LinkedList<>();
306    String cmdName = "sequentialWrite";
307    opts.offer("--oneCon=true");
308    opts.offer("--connCount=10");
309    opts.offer(cmdName);
310    opts.offer("64");
311    PerformanceEvaluation.TestOptions options = null;
312    try {
313      options = PerformanceEvaluation.parseOpts(opts);
314      fail("should fail");
315    } catch (IllegalArgumentException e) {
316      System.out.println(e.getMessage());
317    }
318
319    opts = new LinkedList<>();
320    opts.offer("--connCount=10");
321    opts.offer(cmdName);
322    opts.offer("64");
323
324    options = PerformanceEvaluation.parseOpts(opts);
325    assertNotNull(options);
326    assertNotNull(options.getCmdName());
327    assertEquals(cmdName, options.getCmdName());
328    assertEquals(10, options.getConnCount());
329  }
330
331  @Test
332  public void testParseOptsValueRandom() {
333    Queue<String> opts = new LinkedList<>();
334    String cmdName = "sequentialWrite";
335    opts.offer("--valueRandom");
336    opts.offer("--valueZipf");
337    opts.offer(cmdName);
338    opts.offer("64");
339    PerformanceEvaluation.TestOptions options = null;
340    try {
341      options = PerformanceEvaluation.parseOpts(opts);
342      fail("should fail");
343    } catch (IllegalStateException e) {
344      System.out.println(e.getMessage());
345    }
346
347    opts = new LinkedList<>();
348    opts.offer("--valueRandom");
349    opts.offer(cmdName);
350    opts.offer("64");
351
352    options = PerformanceEvaluation.parseOpts(opts);
353
354    assertNotNull(options);
355    assertNotNull(options.getCmdName());
356    assertEquals(cmdName, options.getCmdName());
357    assertEquals(true, options.valueRandom);
358  }
359
360}