1   /**
2    * Copyright 2010 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.regionserver.wal;
21  
22  import java.io.IOException;
23  import java.util.Random;
24  
25  import org.apache.commons.logging.impl.Log4JLogger;
26  import org.apache.hadoop.hbase.*;
27  import org.apache.log4j.Level;
28  import org.apache.commons.logging.Log;
29  import org.apache.commons.logging.LogFactory;
30  
31  import org.apache.hadoop.util.Tool;
32  import org.apache.hadoop.util.ToolRunner;
33  import org.apache.hadoop.conf.Configuration;
34  import org.apache.hadoop.conf.Configured;
35  import org.apache.hadoop.fs.FileSystem;
36  import org.apache.hadoop.fs.Path;
37  import org.apache.hadoop.hbase.regionserver.HRegion;
38  import org.apache.hadoop.hbase.util.Bytes;
39  import org.apache.hadoop.hbase.ipc.HBaseRPC;
40  
41  import org.junit.Test;
42  import org.junit.experimental.categories.Category;
43  
44  @Category(MediumTests.class)
45  public class TestHLogBench extends Configured implements Tool {
46  
47    static final Log LOG = LogFactory.getLog(TestHLogBench.class);
48    private static final Random r = new Random();
49  
50    private static final byte [] FAMILY = Bytes.toBytes("hlogbenchFamily");
51  
52    // accumulate time here
53    private static int totalTime = 0;
54    private static Object lock = new Object();
55  
56    // the file system where to create the Hlog file
57    protected FileSystem fs;
58  
59    // the number of threads and the number of iterations per thread
60    private int numThreads = 300;
61    private int numIterationsPerThread = 10000;
62  
63    private final HBaseTestingUtility TEST_UTIL = new HBaseTestingUtility();
64    private Path regionRootDir =TEST_UTIL.getDataTestDir("TestHLogBench") ;
65  
66    private boolean appendNoSync = false;
67  
68    public TestHLogBench() {
69      this(null);
70    }
71  
72    private TestHLogBench(Configuration conf) {
73      super(conf);
74      fs = null;
75    }
76  
77    /**
78     * Initialize file system object
79     */
80    public void init() throws IOException {
81      getConf().setQuietMode(true);
82      if (this.fs == null) {
83       this.fs = FileSystem.get(getConf());
84      }
85    }
86  
87    /**
88     * Close down file system
89     */
90    public void close() throws IOException {
91      if (fs != null) {
92        fs.close();
93        fs = null;
94      }
95    }
96  
97    /**
98     * The main run method of TestHLogBench
99     */
100   public int run(String argv[]) throws Exception {
101 
102     int exitCode = -1;
103     int i = 0;
104 
105     // verify that we have enough command line parameters
106     if (argv.length < 4) {
107       printUsage("");
108       return exitCode;
109     }
110 
111     // initialize LogBench
112     try {
113       init();
114     } catch (HBaseRPC.VersionMismatch v) {
115       LOG.warn("Version Mismatch between client and server" +
116                "... command aborted.");
117       return exitCode;
118     } catch (IOException e) {
119       LOG.warn("Bad connection to FS. command aborted.");
120       return exitCode;
121     }
122 
123     try {
124       for (; i < argv.length; i++) {
125         if ("-numThreads".equals(argv[i])) {
126           i++;
127           this.numThreads = Integer.parseInt(argv[i]);
128         } else if ("-numIterationsPerThread".equals(argv[i])) {
129           i++;
130           this.numIterationsPerThread = Integer.parseInt(argv[i]);
131         } else if ("-path".equals(argv[i])) {
132           // get an absolute path using the default file system
133           i++;
134           this.regionRootDir = new Path(argv[i]);
135           this.regionRootDir = regionRootDir.makeQualified(this.fs);
136         } else if ("-nosync".equals(argv[i])) {
137           this.appendNoSync = true;
138         } else {
139           printUsage(argv[i]);
140           return exitCode;
141         }
142       }
143     } catch (NumberFormatException nfe) {
144       LOG.warn("Illegal numThreads or numIterationsPerThread, " +
145                " a positive integer expected");
146       throw nfe;
147     }
148     go();
149     return 0;
150   }
151 
152   private void go() throws IOException, InterruptedException {
153 
154     long start = System.currentTimeMillis();
155     log("Running TestHLogBench with " + numThreads + " threads each doing " +
156         numIterationsPerThread + " HLog appends " +
157         (appendNoSync ? "nosync" : "sync") +
158         " at rootDir " + regionRootDir);
159 
160     // Mock an HRegion
161     byte [] tableName = Bytes.toBytes("table");
162     byte [][] familyNames = new byte [][] { FAMILY };
163     HTableDescriptor htd = new HTableDescriptor();
164     htd.addFamily(new HColumnDescriptor(Bytes.toBytes("f1")));
165     HRegion region = mockRegion(tableName, familyNames, regionRootDir);
166     HLog hlog = region.getLog();
167 
168     // Spin up N threads to each perform M log operations
169     LogWriter [] incrementors = new LogWriter[numThreads];
170     for (int i=0; i<numThreads; i++) {
171       incrementors[i] = new LogWriter(region, tableName, hlog, i, 
172                                       numIterationsPerThread, 
173                                       appendNoSync);
174       incrementors[i].start();
175     }
176 
177     // Wait for threads to finish
178     for (int i=0; i<numThreads; i++) {
179       //log("Waiting for #" + i + " to finish");
180       incrementors[i].join();
181     }
182 
183     // Output statistics
184     long totalOps = numThreads * numIterationsPerThread;
185     log("Operations per second " + ((totalOps * 1000L)/totalTime));
186     log("Average latency in ms " + ((totalTime * 1000L)/totalOps));
187   }
188 
189   /**
190    * Displays format of commands.
191    */
192   private static void printUsage(String cmd) {
193     String prefix = "Usage: java " + TestHLogBench.class.getSimpleName();
194     System.err.println(prefix + cmd + 
195                        " [-numThreads <number>] " +
196                        " [-numIterationsPerThread <number>] " +
197                        " [-path <path where region's root directory is created>]" +
198                        " [-nosync]");
199   }
200 
201   /**
202    * A thread that writes data to an HLog
203    */
204   public static class LogWriter extends Thread {
205 
206     private final HRegion region;
207     private final int threadNumber;
208     private final int numIncrements;
209     private final HLog hlog;
210     private boolean appendNoSync;
211     private byte[] tableName;
212 
213     private int count;
214 
215     public LogWriter(HRegion region, byte[] tableName,
216         HLog log, int threadNumber,
217         int numIncrements, boolean appendNoSync) {
218       this.region = region;
219       this.threadNumber = threadNumber;
220       this.numIncrements = numIncrements;
221       this.hlog = log;
222       this.count = 0;
223       this.appendNoSync = appendNoSync;
224       this.tableName = tableName;
225       setDaemon(true);
226       //log("LogWriter[" + threadNumber + "] instantiated");
227     }
228 
229     @Override
230     public void run() {
231       long now = System.currentTimeMillis();
232       byte [] key = Bytes.toBytes("thisisakey");
233       KeyValue kv = new KeyValue(key, now);
234       WALEdit walEdit = new WALEdit();
235       walEdit.add(kv);
236       HRegionInfo hri = region.getRegionInfo();
237       HTableDescriptor htd = new HTableDescriptor();
238       htd.addFamily(new HColumnDescriptor(Bytes.toBytes("f1")));
239       boolean isMetaRegion = false;
240       long start = System.currentTimeMillis();
241       for (int i=0; i<numIncrements; i++) {
242         try {
243           if (appendNoSync) {
244             hlog.appendNoSync(hri, tableName, walEdit, 
245                               HConstants.DEFAULT_CLUSTER_ID, now, htd);
246           } else {
247             hlog.append(hri, tableName, walEdit, now, htd);
248           }
249         } catch (IOException e) {
250           log("Fatal exception: " + e);
251           e.printStackTrace();
252         }
253         count++;
254       }
255       long tot = System.currentTimeMillis() - start;
256       synchronized (lock) {
257         totalTime += tot;   // update global statistics
258       }
259 
260     }
261   }
262 
263   private static void log(String string) {
264     LOG.info(string);
265   }
266 
267   private byte[][] makeBytes(int numArrays, int arraySize) {
268     byte [][] bytes = new byte[numArrays][];
269     for (int i=0; i<numArrays; i++) {
270       bytes[i] = new byte[arraySize];
271       r.nextBytes(bytes[i]);
272     }
273     return bytes;
274   }
275 
276   /**
277    * Create a dummy region
278    */
279   private HRegion mockRegion(byte[] tableName, byte[][] familyNames,
280                              Path rootDir) throws IOException {
281 
282     HBaseTestingUtility htu = new HBaseTestingUtility();
283     Configuration conf = htu.getConfiguration();
284     conf.setBoolean("hbase.rs.cacheblocksonwrite", true);
285     conf.setBoolean("hbase.hregion.use.incrementnew", true);
286     conf.setBoolean("dfs.support.append", true);
287     FileSystem fs = FileSystem.get(conf);
288     int numQualifiers = 10;
289     byte [][] qualifiers = new byte [numQualifiers][];
290     for (int i=0; i<numQualifiers; i++) qualifiers[i] = Bytes.toBytes("qf" + i);
291     int numRows = 10;
292     byte [][] rows = new byte [numRows][];
293     for (int i=0; i<numRows; i++) rows[i] = Bytes.toBytes("r" + i);
294 
295     // switch off debug message from Region server
296     ((Log4JLogger)HRegion.LOG).getLogger().setLevel(Level.WARN);
297 
298     HTableDescriptor htd = new HTableDescriptor(tableName);
299     for (byte [] family : familyNames)
300       htd.addFamily(new HColumnDescriptor(family));
301 
302     HRegionInfo hri = new HRegionInfo(tableName, Bytes.toBytes(0L), 
303                                       Bytes.toBytes(0xffffffffL));
304     if (fs.exists(rootDir)) {
305       if (!fs.delete(rootDir, true)) {
306         throw new IOException("Failed delete of " + rootDir);
307       }
308     }
309     return HRegion.createHRegion(hri, rootDir, conf, htd);
310   }
311 
312   @Test
313   public void testLogPerformance() throws Exception {
314     TestHLogBench bench = new TestHLogBench();
315     int res;
316     String[] argv = new String[7];
317     argv[0] = "-numThreads";
318     argv[1] = Integer.toString(100);
319     argv[2] = "-numIterationsPerThread";
320     argv[3] = Integer.toString(1000);
321     argv[4] = "-path";
322     argv[5] = TEST_UTIL.getDataTestDir() + "/HlogPerformance";
323     argv[6] = "-nosync";
324     try {
325       res = ToolRunner.run(bench, argv);
326     } finally {
327       bench.close();
328     }
329   }
330 
331   public static void main(String[] argv) throws Exception {
332     TestHLogBench bench = new TestHLogBench();
333     int res;
334     try {
335       res = ToolRunner.run(bench, argv);
336     } finally {
337       bench.close();
338     }
339     System.exit(res);
340   }
341 
342   @org.junit.Rule
343   public org.apache.hadoop.hbase.ResourceCheckerJUnitRule cu =
344     new org.apache.hadoop.hbase.ResourceCheckerJUnitRule();
345 }
346