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.assertThrows;
024import static org.junit.Assert.assertTrue;
025import static org.junit.Assert.fail;
026
027import com.codahale.metrics.Histogram;
028import com.codahale.metrics.Snapshot;
029import com.codahale.metrics.UniformReservoir;
030import java.io.BufferedReader;
031import java.io.ByteArrayInputStream;
032import java.io.File;
033import java.io.FileWriter;
034import java.io.IOException;
035import java.io.InputStreamReader;
036import java.lang.reflect.Constructor;
037import java.lang.reflect.InvocationTargetException;
038import java.nio.charset.StandardCharsets;
039import java.util.LinkedList;
040import java.util.NoSuchElementException;
041import java.util.Properties;
042import java.util.Queue;
043import org.apache.hadoop.fs.FSDataInputStream;
044import org.apache.hadoop.fs.FileSystem;
045import org.apache.hadoop.fs.Path;
046import org.apache.hadoop.hbase.PerformanceEvaluation.RandomReadTest;
047import org.apache.hadoop.hbase.PerformanceEvaluation.Status;
048import org.apache.hadoop.hbase.PerformanceEvaluation.TestOptions;
049import org.apache.hadoop.hbase.client.ColumnFamilyDescriptor;
050import org.apache.hadoop.hbase.client.Connection;
051import org.apache.hadoop.hbase.client.TableDescriptor;
052import org.apache.hadoop.hbase.regionserver.CompactingMemStore;
053import org.apache.hadoop.hbase.testclassification.MiscTests;
054import org.apache.hadoop.hbase.testclassification.SmallTests;
055import org.apache.hadoop.hbase.util.GsonUtil;
056import org.junit.ClassRule;
057import org.junit.Test;
058import org.junit.experimental.categories.Category;
059
060import org.apache.hbase.thirdparty.com.google.gson.Gson;
061
062@Category({ MiscTests.class, SmallTests.class })
063public class TestPerformanceEvaluation {
064  @ClassRule
065  public static final HBaseClassTestRule CLASS_RULE =
066    HBaseClassTestRule.forClass(TestPerformanceEvaluation.class);
067
068  private static final HBaseTestingUtil HTU = new HBaseTestingUtil();
069
070  @Test
071  public void testDefaultInMemoryCompaction() {
072    PerformanceEvaluation.TestOptions defaultOpts = new PerformanceEvaluation.TestOptions();
073    assertEquals(CompactingMemStore.COMPACTING_MEMSTORE_TYPE_DEFAULT,
074      defaultOpts.getInMemoryCompaction().toString());
075    TableDescriptor tableDescriptor = PerformanceEvaluation.getTableDescriptor(defaultOpts);
076    for (ColumnFamilyDescriptor familyDescriptor : tableDescriptor.getColumnFamilies()) {
077      assertEquals(CompactingMemStore.COMPACTING_MEMSTORE_TYPE_DEFAULT,
078        familyDescriptor.getInMemoryCompaction().toString());
079    }
080  }
081
082  @Test
083  public void testSerialization() {
084    PerformanceEvaluation.TestOptions options = new PerformanceEvaluation.TestOptions();
085    assertFalse(options.isAutoFlush());
086    options.setAutoFlush(true);
087    Gson gson = GsonUtil.createGson().create();
088    String optionsString = gson.toJson(options);
089    PerformanceEvaluation.TestOptions optionsDeserialized =
090      gson.fromJson(optionsString, PerformanceEvaluation.TestOptions.class);
091    assertTrue(optionsDeserialized.isAutoFlush());
092  }
093
094  /**
095   * Exercise the mr spec writing. Simple assertions to make sure it is basically working.
096   */
097  @Test
098  public void testWriteInputFile() throws IOException {
099    TestOptions opts = new PerformanceEvaluation.TestOptions();
100    final int clients = 10;
101    opts.setNumClientThreads(clients);
102    opts.setPerClientRunRows(10);
103    Path dir =
104      PerformanceEvaluation.writeInputFile(HTU.getConfiguration(), opts, HTU.getDataTestDir());
105    FileSystem fs = FileSystem.get(HTU.getConfiguration());
106    Path p = new Path(dir, PerformanceEvaluation.JOB_INPUT_FILENAME);
107    long len = fs.getFileStatus(p).getLen();
108    assertTrue(len > 0);
109    byte[] content = new byte[(int) len];
110    try (FSDataInputStream dis = fs.open(p)) {
111      dis.readFully(content);
112      BufferedReader br = new BufferedReader(
113        new InputStreamReader(new ByteArrayInputStream(content), StandardCharsets.UTF_8));
114      int count = 0;
115      while (br.readLine() != null) {
116        count++;
117      }
118      assertEquals(clients, count);
119    }
120  }
121
122  @Test
123  public void testSizeCalculation() {
124    TestOptions opts = new PerformanceEvaluation.TestOptions();
125    opts = PerformanceEvaluation.calculateRowsAndSize(opts);
126    long rows = opts.getPerClientRunRows();
127    // Default row count
128    final int defaultPerClientRunRows = 1024 * 1024;
129    assertEquals(defaultPerClientRunRows, rows);
130    // If size is 2G, then twice the row count.
131    opts.setSize(2.0f);
132    opts = PerformanceEvaluation.calculateRowsAndSize(opts);
133    assertEquals(defaultPerClientRunRows * 2, opts.getPerClientRunRows());
134    // If two clients, then they get half the rows each.
135    opts.setNumClientThreads(2);
136    opts = PerformanceEvaluation.calculateRowsAndSize(opts);
137    assertEquals(defaultPerClientRunRows, opts.getPerClientRunRows());
138    // What if valueSize is 'random'? Then half of the valueSize so twice the rows.
139    opts.valueRandom = true;
140    opts = PerformanceEvaluation.calculateRowsAndSize(opts);
141    assertEquals(defaultPerClientRunRows * 2, opts.getPerClientRunRows());
142  }
143
144  @Test
145  public void testRandomReadCalculation() {
146    TestOptions opts = new PerformanceEvaluation.TestOptions();
147    opts = PerformanceEvaluation.calculateRowsAndSize(opts);
148    long rows = opts.getPerClientRunRows();
149    // Default row count
150    final int defaultPerClientRunRows = 1024 * 1024;
151    assertEquals(defaultPerClientRunRows, rows);
152    // If size is 2G, then twice the row count.
153    opts.setSize(2.0f);
154    opts.setPerClientRunRows(1000);
155    opts.setCmdName(PerformanceEvaluation.RANDOM_READ);
156    opts = PerformanceEvaluation.calculateRowsAndSize(opts);
157    assertEquals(1000, opts.getPerClientRunRows());
158    // If two clients, then they get half the rows each.
159    opts.setNumClientThreads(2);
160    opts = PerformanceEvaluation.calculateRowsAndSize(opts);
161    assertEquals(1000, opts.getPerClientRunRows());
162    // assuming we will get one before this loop expires
163    boolean foundValue = false;
164    for (int i = 0; i < 10000000; i++) {
165      long randomRow = PerformanceEvaluation.generateRandomRow(opts.totalRows);
166      if (randomRow > 1000) {
167        foundValue = true;
168        break;
169      }
170    }
171    assertTrue("We need to get a value more than 1000", foundValue);
172  }
173
174  @Test
175  public void testZipfian() throws NoSuchMethodException, SecurityException, InstantiationException,
176    IllegalAccessException, IllegalArgumentException, InvocationTargetException {
177    TestOptions opts = new PerformanceEvaluation.TestOptions();
178    opts.setValueZipf(true);
179    final int valueSize = 1024;
180    opts.setValueSize(valueSize);
181    RandomReadTest rrt = new RandomReadTest(null, opts, null);
182    Constructor<?> ctor =
183      Histogram.class.getDeclaredConstructor(com.codahale.metrics.Reservoir.class);
184    ctor.setAccessible(true);
185    Histogram histogram = (Histogram) ctor.newInstance(new UniformReservoir(1024 * 500));
186    for (int i = 0; i < 100; i++) {
187      histogram.update(rrt.getValueLength());
188    }
189    Snapshot snapshot = histogram.getSnapshot();
190    double stddev = snapshot.getStdDev();
191    assertTrue(stddev != 0 && stddev != 1.0);
192    assertTrue(snapshot.getStdDev() != 0);
193    double median = snapshot.getMedian();
194    assertTrue(median != 0 && median != 1 && median != valueSize);
195  }
196
197  @Test
198  public void testSetBufferSizeOption() {
199    TestOptions opts = new PerformanceEvaluation.TestOptions();
200    long bufferSize = opts.getBufferSize();
201    assertEquals(bufferSize, 2L * 1024L * 1024L);
202    opts.setBufferSize(64L * 1024L);
203    bufferSize = opts.getBufferSize();
204    assertEquals(bufferSize, 64L * 1024L);
205  }
206
207  @Test
208  public void testParseOptsWithThreads() {
209    Queue<String> opts = new LinkedList<>();
210    String cmdName = "sequentialWrite";
211    int threads = 1;
212    opts.offer(cmdName);
213    opts.offer(String.valueOf(threads));
214    PerformanceEvaluation.TestOptions options = PerformanceEvaluation.parseOpts(opts);
215    assertNotNull(options);
216    assertNotNull(options.getCmdName());
217    assertEquals(cmdName, options.getCmdName());
218    assertEquals(threads, options.getNumClientThreads());
219  }
220
221  @Test
222  public void testParseOptsWrongThreads() {
223    Queue<String> opts = new LinkedList<>();
224    String cmdName = "sequentialWrite";
225    opts.offer(cmdName);
226    opts.offer("qq");
227    try {
228      PerformanceEvaluation.parseOpts(opts);
229    } catch (IllegalArgumentException e) {
230      System.out.println(e.getMessage());
231      assertEquals("Command " + cmdName + " does not have threads number", e.getMessage());
232      assertTrue(e.getCause() instanceof NumberFormatException);
233    }
234  }
235
236  @Test
237  public void testParseOptsNoThreads() {
238    Queue<String> opts = new LinkedList<>();
239    String cmdName = "sequentialWrite";
240    try {
241      PerformanceEvaluation.parseOpts(opts);
242    } catch (IllegalArgumentException e) {
243      System.out.println(e.getMessage());
244      assertEquals("Command " + cmdName + " does not have threads number", e.getMessage());
245      assertTrue(e.getCause() instanceof NoSuchElementException);
246    }
247  }
248
249  @Test
250  public void testParseOptsMultiPuts() {
251    Queue<String> opts = new LinkedList<>();
252    String cmdName = "sequentialWrite";
253    opts.offer("--multiPut=10");
254    opts.offer(cmdName);
255    opts.offer("64");
256    PerformanceEvaluation.TestOptions options = null;
257    try {
258      options = PerformanceEvaluation.parseOpts(opts);
259      fail("should fail");
260    } catch (IllegalArgumentException e) {
261      System.out.println(e.getMessage());
262    }
263
264    // Re-create options
265    opts = new LinkedList<>();
266    opts.offer("--autoFlush=true");
267    opts.offer("--multiPut=10");
268    opts.offer(cmdName);
269    opts.offer("64");
270
271    options = PerformanceEvaluation.parseOpts(opts);
272    assertNotNull(options);
273    assertNotNull(options.getCmdName());
274    assertEquals(cmdName, options.getCmdName());
275    assertEquals(10, options.getMultiPut());
276  }
277
278  @Test
279  public void testParseOptsMultiPutsAndAutoFlushOrder() {
280    Queue<String> opts = new LinkedList<>();
281    String cmdName = "sequentialWrite";
282    String cmdMultiPut = "--multiPut=10";
283    String cmdAutoFlush = "--autoFlush=true";
284    opts.offer(cmdAutoFlush);
285    opts.offer(cmdMultiPut);
286    opts.offer(cmdName);
287    opts.offer("64");
288    PerformanceEvaluation.TestOptions options = null;
289    options = PerformanceEvaluation.parseOpts(opts);
290    assertNotNull(options);
291    assertEquals(true, options.autoFlush);
292    assertEquals(10, options.getMultiPut());
293
294    // Change the order of AutoFlush and Multiput
295    opts = new LinkedList<>();
296    opts.offer(cmdMultiPut);
297    opts.offer(cmdAutoFlush);
298    opts.offer(cmdName);
299    opts.offer("64");
300
301    options = null;
302    options = PerformanceEvaluation.parseOpts(opts);
303    assertNotNull(options);
304    assertEquals(10, options.getMultiPut());
305    assertEquals(true, options.autoFlush);
306  }
307
308  @Test
309  public void testParseOptsConnCount() {
310    Queue<String> opts = new LinkedList<>();
311    String cmdName = "sequentialWrite";
312    opts.offer("--oneCon=true");
313    opts.offer("--connCount=10");
314    opts.offer(cmdName);
315    opts.offer("64");
316    PerformanceEvaluation.TestOptions options = null;
317    try {
318      options = PerformanceEvaluation.parseOpts(opts);
319      fail("should fail");
320    } catch (IllegalArgumentException e) {
321      System.out.println(e.getMessage());
322    }
323
324    opts = new LinkedList<>();
325    opts.offer("--connCount=10");
326    opts.offer(cmdName);
327    opts.offer("64");
328
329    options = PerformanceEvaluation.parseOpts(opts);
330    assertNotNull(options);
331    assertNotNull(options.getCmdName());
332    assertEquals(cmdName, options.getCmdName());
333    assertEquals(10, options.getConnCount());
334  }
335
336  @Test
337  public void testParseOptsValueRandom() {
338    Queue<String> opts = new LinkedList<>();
339    String cmdName = "sequentialWrite";
340    opts.offer("--valueRandom");
341    opts.offer("--valueZipf");
342    opts.offer(cmdName);
343    opts.offer("64");
344    PerformanceEvaluation.TestOptions options = null;
345    try {
346      options = PerformanceEvaluation.parseOpts(opts);
347      fail("should fail");
348    } catch (IllegalStateException e) {
349      System.out.println(e.getMessage());
350    }
351
352    opts = new LinkedList<>();
353    opts.offer("--valueRandom");
354    opts.offer(cmdName);
355    opts.offer("64");
356
357    options = PerformanceEvaluation.parseOpts(opts);
358
359    assertNotNull(options);
360    assertNotNull(options.getCmdName());
361    assertEquals(cmdName, options.getCmdName());
362    assertEquals(true, options.valueRandom);
363  }
364
365  @Test
366  public void testCustomTestClassOptions() throws IOException {
367    Queue<String> opts = new LinkedList<>();
368    // create custom properties that can be used for a custom test class
369    Properties commandProps = new Properties();
370    commandProps.put("prop1", "val1");
371    String cmdPropsFilePath =
372      this.getClass().getClassLoader().getResource("").getPath() + "cmd_properties.txt";
373    FileWriter writer = new FileWriter(new File(cmdPropsFilePath));
374    commandProps.store(writer, null);
375    // create opts for the custom test class - commandPropertiesFile, testClassName
376    opts.offer("--commandPropertiesFile=" + "cmd_properties.txt");
377    String testClassName = "org.apache.hadoop.hbase.TestPerformanceEvaluation$PESampleTestImpl";
378    opts.offer(testClassName);
379    opts.offer("1");
380    PerformanceEvaluation.TestOptions options = PerformanceEvaluation.parseOpts(opts);
381    assertNotNull(options);
382    assertNotNull(options.getCmdName());
383    assertEquals(testClassName, options.getCmdName());
384    assertNotNull(options.getCommandProperties());
385    assertEquals("val1", options.getCommandProperties().get("prop1"));
386  }
387
388  class PESampleTestImpl extends PerformanceEvaluation.Test {
389
390    PESampleTestImpl(Connection con, TestOptions options, Status status) {
391      super(con, options, status);
392    }
393
394    @Override
395    void onStartup() throws IOException {
396    }
397
398    @Override
399    void onTakedown() throws IOException {
400    }
401
402    @Override
403    boolean testRow(long i, long startTime) throws IOException, InterruptedException {
404      return false;
405    }
406  }
407
408  @Test
409  public void testParseBooleanFlags() {
410    final Queue<String> opts = new LinkedList<>();
411    opts.offer("--valueRandom");
412    opts.offer("--autoFlush"); // default: false
413    opts.offer("--inmemory=true"); // default: false
414    opts.offer("--writeToWAL=false"); // default: true
415    opts.offer(PerformanceEvaluation.RANDOM_READ);
416    opts.offer("1");
417
418    final PerformanceEvaluation.TestOptions options = PerformanceEvaluation.parseOpts(opts);
419    assertTrue(options.valueRandom);
420    assertTrue(options.autoFlush);
421    assertTrue(options.inMemoryCF);
422    assertFalse(options.writeToWAL);
423    assertEquals(PerformanceEvaluation.RANDOM_READ, options.getCmdName());
424    assertEquals(1, options.getNumClientThreads());
425  }
426
427  @Test
428  public void testOptionMissingValue() {
429    final Queue<String> opts = new LinkedList<>();
430    opts.offer("--presplit");
431    assertThrows(IllegalArgumentException.class, () -> PerformanceEvaluation.parseOpts(opts));
432  }
433}