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.DataInputStream;
23  import java.io.FileInputStream;
24  import java.io.IOException;
25  import java.io.RandomAccessFile;
26  import java.util.ArrayList;
27  import java.util.Arrays;
28  import java.util.List;
29  import java.util.Random;
30  import java.util.concurrent.Callable;
31  import java.util.concurrent.atomic.AtomicLong;
32  import java.util.concurrent.atomic.AtomicReference;
33  
34  import org.apache.commons.logging.Log;
35  import org.apache.commons.logging.LogFactory;
36  import org.apache.hadoop.hbase.client.Get;
37  import org.apache.hadoop.hbase.client.HBaseAdmin;
38  import org.apache.hadoop.hbase.client.HTable;
39  import org.apache.hadoop.hbase.client.Put;
40  import org.apache.hadoop.hbase.client.Result;
41  import org.apache.hadoop.hbase.util.Bytes;
42  import org.apache.hadoop.hbase.util.InfoServer;
43  
44  
45  /**
46   * Script used evaluating HBase performance and scalability.  Runs a HBase
47   * client that steps through one of a set of hardcoded tests or 'experiments'
48   * (e.g. a random reads test, a random writes test, etc.). Pass on the
49   * command-line which test to run and how many clients are participating in
50   * this experiment. Run <code>java VerifiableEditor --help</code> to
51   * obtain usage.
52   * 
53   * <p>This class sets up and runs the evaluation programs described in
54   * Section 7, <i>Performance Evaluation</i>, of the <a
55   * href="http://labs.google.com/papers/bigtable.html">Bigtable</a>
56   * paper, pages 8-10.
57   * 
58   * <p>If number of clients > 1, we start up a MapReduce job. Each map task
59   * runs an individual client. Each client does about 1GB of data.
60   */
61  public class VerifiableEditor {
62    protected static final Log LOG = LogFactory.getLog(VerifiableEditor.class.getName());
63    
64    private static final int NUM_WRITE_THREADS = 40;
65    
66    public static final byte [] TABLE_NAME = Bytes.toBytes("VerifiableEditor");
67    public static final byte [] FAMILY_NAME = Bytes.toBytes("info");
68    public static final byte [] QUALIFIER_NAME = Bytes.toBytes("data");
69  
70    protected static final HTableDescriptor TABLE_DESCRIPTOR;
71    static {
72      TABLE_DESCRIPTOR = new HTableDescriptor(TABLE_NAME);
73      TABLE_DESCRIPTOR.addFamily(new HColumnDescriptor(FAMILY_NAME));
74    }
75  
76    volatile HBaseConfiguration conf;
77  
78    /**
79     * Constructor
80     * @param c Configuration object
81     */
82    public VerifiableEditor(final HBaseConfiguration c) {
83      this.conf = c;
84    }
85  
86    private byte [] getDataToWrite(String clientId, long curWrite) {
87      return Bytes.toBytes(String.valueOf(curWrite) + "<" + clientId + ">");
88    }
89  
90    private class Writer implements Callable<Integer> {
91      private AtomicLong randomSeedGenerator;
92  
93      private ThreadLocal<Long> randomSeed = new ThreadLocal<Long>() {
94        protected Long initialValue() {
95          return randomSeedGenerator.incrementAndGet();
96        }
97      };
98  
99      public Writer(String args[]) {
100       this.randomSeedGenerator = new AtomicLong(System.currentTimeMillis());
101     }
102   
103     private RandomAccessFile openLocalRecorder() throws IOException {
104       RandomAccessFile localRecorder = new RandomAccessFile(
105         "/dev/shm/hbase-verifiableeditor-" + randomSeed.get(),
106         "rws");
107       localRecorder.seek(0);
108       localRecorder.writeLong(randomSeed.get());
109       localRecorder.writeLong(-1);
110       return localRecorder;
111     }
112   
113     private void recordIteration(RandomAccessFile raf,
114                                  long iteration) throws IOException {
115       raf.seek(8);
116       raf.writeLong(iteration);
117     }
118  
119 
120     public Integer call() throws IOException  {
121       createTableIfMissing();
122       
123       final AtomicReference<Throwable> err = new AtomicReference<Throwable>();
124       List<Thread> threads = new ArrayList<Thread>();
125 
126       for (int i = 0; i < NUM_WRITE_THREADS; i++) {
127         Thread thr = new Thread() {
128           public void run() {
129             try {
130               doWrites();
131             } catch (Throwable t) {
132               err.set(t);
133             }
134           }
135         };
136         threads.add(thr);
137         thr.start();
138       }
139       for (Thread thr : threads) {
140         try { thr.join(); } catch (InterruptedException ie) {}
141       }
142       if (err.get() != null) {
143         throw new RuntimeException(err.get());
144       }
145 
146       return 0;
147     }
148 
149     private void doWrites() throws IOException {
150       RandomAccessFile recorder = openLocalRecorder();
151       HTable table = new HTable(conf, TABLE_NAME);
152       Random r = new Random(randomSeed.get());
153       boolean stop = false;
154       long iteration = 0;
155       String clientId = String.valueOf(randomSeed.get());
156       while (!stop) {
157         int curWrite = r.nextInt();
158         byte[] curData = getDataToWrite(clientId, curWrite);
159         Put p = new Put(curData /* row */);
160         p.add(FAMILY_NAME, QUALIFIER_NAME, curData);
161         table.put(p);
162 
163         recordIteration(recorder, iteration);
164         iteration++;
165 
166         if (iteration % 1000 == 0) {
167           LOG.info("Client " + clientId + " written " + iteration + " iterations");
168         }
169       }
170     }
171   }
172 
173   private class Verifier implements Callable<Integer> {
174     private final long randomSeed;
175     private final long verifyUpTo;
176 
177     public Verifier(List<String> args) throws IOException {
178       if (args.size() != 1) {
179         printUsage();
180         throw new RuntimeException("bad usage");
181       }
182 
183       DataInputStream in = new DataInputStream(new FileInputStream(args.get(0)));
184       try {
185         randomSeed = in.readLong();
186         verifyUpTo = in.readLong();
187       } finally {
188         in.close();
189       }
190     }
191 
192     public Integer call() throws IOException {
193       final Random r = new Random(randomSeed);
194       final String clientId = String.valueOf(randomSeed);
195 
196       List<Thread> threads = new ArrayList<Thread>();
197       final AtomicReference<Throwable> err = new AtomicReference<Throwable>();
198 
199       final AtomicLong curIteration = new AtomicLong();
200 
201       for (int i = 0; i < 10; i++) {
202         Thread thr = new Thread() {
203           public void run() {
204             try {
205               HTable table = new HTable(conf, TABLE_NAME);
206               while (curIteration.get() < verifyUpTo) {
207                 int curWrite;
208                 long myIteration;
209                 synchronized (r) {
210                   curWrite = r.nextInt();
211                   myIteration = curIteration.getAndIncrement();
212                 }
213 
214                 byte[] curData = getDataToWrite(clientId, curWrite);
215                 Get g = new Get(curData);
216                 Result res = table.get(g);
217                 byte[] gotValue = res.getValue(FAMILY_NAME, QUALIFIER_NAME);
218                 if (! Bytes.equals(curData, gotValue)) {
219                   throw new RuntimeException("VERIFICATION FAILED. " +
220                     "iteration=" + myIteration + "/" + verifyUpTo +
221                     " seed=" + randomSeed +
222                     " expected=" + (curData != null ? Bytes.toStringBinary(curData) : "null") +
223                     " got=" + (gotValue != null ? Bytes.toStringBinary(gotValue) : "null"));
224                 }
225               }
226             } catch (Throwable t) {
227               err.set(t);
228             }
229           }
230         };
231         threads.add(thr);
232         thr.start();
233       }
234       for (Thread thr : threads) {
235         try { thr.join(); } catch (InterruptedException ie) {}
236       }
237       if (err.get() != null) {
238         throw new RuntimeException(err.get());
239       }
240 
241       LOG.info("Successfully verified " + verifyUpTo + " writes from " + randomSeed);
242 
243       return 0;
244     }
245   }
246  
247 
248   protected void printUsage() {
249     printUsage(null);
250   }
251   
252   protected void printUsage(final String message) {
253     if (message != null && message.length() > 0) {
254       System.err.println(message);
255     }
256     System.err.println("Usage: java " + this.getClass().getName() + " \\");
257     System.err.println(" [writer | verify <writerlog>]");
258   }
259 
260   private void createTableIfMissing() throws IOException {
261     try {
262       HBaseAdmin admin = new HBaseAdmin(conf);
263       admin.createTable(TABLE_DESCRIPTOR);
264       LOG.info("Created table!");
265     } catch (TableExistsException tee) {
266     }
267   }
268 
269 
270 
271   public int doCommandLine(String args[]) {
272     if (args.length < 1) {
273       printUsage();
274       return 1;
275     }
276 
277     List<String> toolArgs = Arrays.<String>asList(args).subList(1, args.length);
278 
279     InfoServer infoServer = null;
280     try {
281       infoServer = new InfoServer(
282       	"static", "0.0.0.0", 0, true);      
283       infoServer.start();
284       
285       Callable<Integer> tool = null;
286 
287       // Pick tool
288       if (args[0].equals("writer")) {
289         tool = new Writer(args);
290       } else if (args[0].equals("verify")) {
291         tool = new Verifier(toolArgs);
292       } else {
293         printUsage("unknown tool: " + args[0]);
294         return 1;
295       }
296 
297       return tool.call();
298     } catch (Exception e) {
299       throw new RuntimeException(e);
300     } finally {
301       try {
302     	if (infoServer != null)
303     	  infoServer.stop();
304       } catch (Exception e) {
305     	LOG.error("Couldn't stop info server", e);
306 	  }
307     }
308   }
309 
310 
311   /**
312    * @param args
313    */
314   public static void main(final String[] args) {
315     HBaseConfiguration c = new HBaseConfiguration();
316     System.exit(new VerifiableEditor(c).doCommandLine(args));
317   }
318 }