1   /**
2    * Copyright 2007 The Apache Software Foundation
3    *
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *     http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS,
16   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17   * See the License for the specific language governing permissions and
18   * limitations under the License.
19   */
20  package org.apache.hadoop.hbase;
21  
22  import java.io.IOException;
23  import java.nio.ByteBuffer;
24  import java.util.Random;
25  
26  import org.apache.commons.logging.Log;
27  import org.apache.commons.logging.LogFactory;
28  import org.apache.commons.math.random.RandomData;
29  import org.apache.commons.math.random.RandomDataImpl;
30  import org.apache.hadoop.conf.Configuration;
31  import org.apache.hadoop.fs.FileSystem;
32  import org.apache.hadoop.fs.Path;
33  import org.apache.hadoop.hbase.io.ImmutableBytesWritable;
34  import org.apache.hadoop.hbase.io.hfile.HFile;
35  import org.apache.hadoop.hbase.io.hfile.HFileScanner;
36  import org.apache.hadoop.hbase.io.hfile.Compression;
37  import org.apache.hadoop.hbase.util.Bytes;
38  
39  /**
40   * <p>
41   * This class runs performance benchmarks for {@link HFile}.
42   * </p>
43   */
44  public class HFilePerformanceEvaluation {
45  
46    private static final int ROW_LENGTH = 10;
47    private static final int ROW_COUNT = 1000000;
48    private static final int RFILE_BLOCKSIZE = 8 * 1024;
49  
50    static final Log LOG =
51      LogFactory.getLog(HFilePerformanceEvaluation.class.getName());
52  
53    static byte [] format(final int i) {
54      String v = Integer.toString(i);
55      return Bytes.toBytes("0000000000".substring(v.length()) + v);
56    }
57  
58    static ImmutableBytesWritable format(final int i, ImmutableBytesWritable w) {
59      w.set(format(i));
60      return w;
61    }
62  
63    private void runBenchmarks() throws Exception {
64      final Configuration conf = new Configuration();
65      final FileSystem fs = FileSystem.get(conf);
66      final Path mf = fs.makeQualified(new Path("performanceevaluation.mapfile"));
67      if (fs.exists(mf)) {
68        fs.delete(mf, true);
69      }
70  
71      runBenchmark(new SequentialWriteBenchmark(conf, fs, mf, ROW_COUNT),
72          ROW_COUNT);
73      PerformanceEvaluationCommons.concurrentReads(new Runnable() {
74        public void run() {
75          try {
76            runBenchmark(new UniformRandomSmallScan(conf, fs, mf, ROW_COUNT),
77              ROW_COUNT);
78          } catch (Exception e) {
79            e.printStackTrace();
80          }
81        }
82      });
83      PerformanceEvaluationCommons.concurrentReads(new Runnable() {
84        public void run() {
85          try {
86            runBenchmark(new UniformRandomReadBenchmark(conf, fs, mf, ROW_COUNT),
87                ROW_COUNT);
88          } catch (Exception e) {
89            e.printStackTrace();
90          }
91        }
92      });
93      PerformanceEvaluationCommons.concurrentReads(new Runnable() {
94        public void run() {
95          try {
96            runBenchmark(new GaussianRandomReadBenchmark(conf, fs, mf, ROW_COUNT),
97                ROW_COUNT);
98          } catch (Exception e) {
99            e.printStackTrace();
100         }
101       }
102     });
103     PerformanceEvaluationCommons.concurrentReads(new Runnable() {
104       public void run() {
105         try {
106           runBenchmark(new SequentialReadBenchmark(conf, fs, mf, ROW_COUNT),
107               ROW_COUNT);
108         } catch (Exception e) {
109           e.printStackTrace();
110         }
111       }
112     });
113 
114   }
115 
116   protected void runBenchmark(RowOrientedBenchmark benchmark, int rowCount)
117     throws Exception {
118     LOG.info("Running " + benchmark.getClass().getSimpleName() + " for " +
119         rowCount + " rows.");
120     long elapsedTime = benchmark.run();
121     LOG.info("Running " + benchmark.getClass().getSimpleName() + " for " +
122         rowCount + " rows took " + elapsedTime + "ms.");
123   }
124 
125   static abstract class RowOrientedBenchmark {
126 
127     protected final Configuration conf;
128     protected final FileSystem fs;
129     protected final Path mf;
130     protected final int totalRows;
131 
132     public RowOrientedBenchmark(Configuration conf, FileSystem fs, Path mf,
133         int totalRows) {
134       this.conf = conf;
135       this.fs = fs;
136       this.mf = mf;
137       this.totalRows = totalRows;
138     }
139 
140     void setUp() throws Exception {
141       // do nothing
142     }
143 
144     abstract void doRow(int i) throws Exception;
145 
146     protected int getReportingPeriod() {
147       return this.totalRows / 10;
148     }
149 
150     void tearDown() throws Exception {
151       // do nothing
152     }
153 
154     /**
155      * Run benchmark
156      * @return elapsed time.
157      * @throws Exception
158      */
159     long run() throws Exception {
160       long elapsedTime;
161       setUp();
162       long startTime = System.currentTimeMillis();
163       try {
164         for (int i = 0; i < totalRows; i++) {
165           if (i > 0 && i % getReportingPeriod() == 0) {
166             LOG.info("Processed " + i + " rows.");
167           }
168           doRow(i);
169         }
170         elapsedTime = System.currentTimeMillis() - startTime;
171       } finally {
172         tearDown();
173       }
174       return elapsedTime;
175     }
176 
177   }
178 
179   static class SequentialWriteBenchmark extends RowOrientedBenchmark {
180     protected HFile.Writer writer;
181     private Random random = new Random();
182     private byte[] bytes = new byte[ROW_LENGTH];
183 
184     public SequentialWriteBenchmark(Configuration conf, FileSystem fs, Path mf,
185         int totalRows) {
186       super(conf, fs, mf, totalRows);
187     }
188 
189     @Override
190     void setUp() throws Exception {
191       writer = new HFile.Writer(this.fs, this.mf, RFILE_BLOCKSIZE, (Compression.Algorithm) null, null);
192     }
193 
194     @Override
195     void doRow(int i) throws Exception {
196       writer.append(format(i), generateValue());
197     }
198 
199     private byte[] generateValue() {
200       random.nextBytes(bytes);
201       return bytes;
202     }
203 
204     @Override
205     protected int getReportingPeriod() {
206       return this.totalRows; // don't report progress
207     }
208 
209     @Override
210     void tearDown() throws Exception {
211       writer.close();
212     }
213 
214   }
215 
216   static abstract class ReadBenchmark extends RowOrientedBenchmark {
217 
218     protected HFile.Reader reader;
219 
220     public ReadBenchmark(Configuration conf, FileSystem fs, Path mf,
221         int totalRows) {
222       super(conf, fs, mf, totalRows);
223     }
224 
225     @Override
226     void setUp() throws Exception {
227       reader = new HFile.Reader(this.fs, this.mf, null, false);
228       this.reader.loadFileInfo();
229     }
230 
231     @Override
232     void tearDown() throws Exception {
233       reader.close();
234     }
235 
236   }
237 
238   static class SequentialReadBenchmark extends ReadBenchmark {
239     private HFileScanner scanner;
240 
241     public SequentialReadBenchmark(Configuration conf, FileSystem fs,
242       Path mf, int totalRows) {
243       super(conf, fs, mf, totalRows);
244     }
245 
246     @Override
247     void setUp() throws Exception {
248       super.setUp();
249       this.scanner = this.reader.getScanner(false, false);
250       this.scanner.seekTo();
251     }
252 
253     @Override
254     void doRow(int i) throws Exception {
255       if (this.scanner.next()) {
256         ByteBuffer k = this.scanner.getKey();
257         PerformanceEvaluationCommons.assertKey(format(i + 1), k);
258         ByteBuffer v = scanner.getValue();
259         PerformanceEvaluationCommons.assertValueSize(v.limit(), ROW_LENGTH);
260       }
261     }
262 
263     @Override
264     protected int getReportingPeriod() {
265       return this.totalRows; // don't report progress
266     }
267 
268   }
269 
270   static class UniformRandomReadBenchmark extends ReadBenchmark {
271 
272     private Random random = new Random();
273 
274     public UniformRandomReadBenchmark(Configuration conf, FileSystem fs,
275         Path mf, int totalRows) {
276       super(conf, fs, mf, totalRows);
277     }
278 
279     @Override
280     void doRow(int i) throws Exception {
281       HFileScanner scanner = this.reader.getScanner(false, true);
282       byte [] b = getRandomRow();
283       scanner.seekTo(b);
284       ByteBuffer k = scanner.getKey();
285       PerformanceEvaluationCommons.assertKey(b, k);
286       ByteBuffer v = scanner.getValue();
287       PerformanceEvaluationCommons.assertValueSize(v.limit(), ROW_LENGTH);
288     }
289 
290     private byte [] getRandomRow() {
291       return format(random.nextInt(totalRows));
292     }
293   }
294 
295   static class UniformRandomSmallScan extends ReadBenchmark {
296     private Random random = new Random();
297 
298     public UniformRandomSmallScan(Configuration conf, FileSystem fs,
299         Path mf, int totalRows) {
300       super(conf, fs, mf, totalRows/10);
301     }
302 
303     @Override
304     void doRow(int i) throws Exception {
305       HFileScanner scanner = this.reader.getScanner(false, false);
306       byte [] b = getRandomRow();
307       if (scanner.seekTo(b) != 0) {
308         System.out.println("Nonexistent row: " + new String(b));
309         return;
310       }
311       ByteBuffer k = scanner.getKey();
312       PerformanceEvaluationCommons.assertKey(b, k);
313       // System.out.println("Found row: " + new String(b));
314       for (int ii = 0; ii < 30; ii++) {
315         if (!scanner.next()) {
316           System.out.println("NOTHING FOLLOWS");
317         }
318         ByteBuffer v = scanner.getValue();
319         PerformanceEvaluationCommons.assertValueSize(v.limit(), ROW_LENGTH);
320       }
321     }
322 
323     private byte [] getRandomRow() {
324       return format(random.nextInt(totalRows));
325     }
326   }
327 
328   static class GaussianRandomReadBenchmark extends ReadBenchmark {
329 
330     private RandomData randomData = new RandomDataImpl();
331 
332     public GaussianRandomReadBenchmark(Configuration conf, FileSystem fs,
333         Path mf, int totalRows) {
334       super(conf, fs, mf, totalRows);
335     }
336 
337     @Override
338     void doRow(int i) throws Exception {
339       HFileScanner scanner = this.reader.getScanner(false, true);
340       scanner.seekTo(getGaussianRandomRowBytes());
341       for (int ii = 0; ii < 30; ii++) {
342         if (!scanner.next()) {
343           System.out.println("NOTHING FOLLOWS");
344         }
345         scanner.getKey();
346         scanner.getValue();
347       }
348     }
349 
350     private byte [] getGaussianRandomRowBytes() {
351       int r = (int) randomData.nextGaussian((double)totalRows / 2.0,
352           (double)totalRows / 10.0);
353       return format(r);
354     }
355   }
356 
357   /**
358    * @param args
359    * @throws Exception
360    * @throws IOException
361    */
362   public static void main(String[] args) throws Exception {
363     new HFilePerformanceEvaluation().runBenchmarks();
364   }
365 }