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