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