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