View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.hadoop.hbase.wal;
19  
20  import java.io.FileNotFoundException;
21  import java.io.IOException;
22  import java.io.PrintStream;
23  import java.util.ArrayList;
24  import java.util.Date;
25  import java.util.HashMap;
26  import java.util.Iterator;
27  import java.util.List;
28  import java.util.Map;
29  
30  import org.apache.commons.cli.CommandLine;
31  import org.apache.commons.cli.CommandLineParser;
32  import org.apache.commons.cli.HelpFormatter;
33  import org.apache.commons.cli.Options;
34  import org.apache.commons.cli.ParseException;
35  import org.apache.commons.cli.PosixParser;
36  import org.apache.hadoop.hbase.classification.InterfaceAudience;
37  import org.apache.hadoop.hbase.classification.InterfaceStability;
38  import org.apache.hadoop.conf.Configuration;
39  import org.apache.hadoop.fs.FileSystem;
40  import org.apache.hadoop.fs.Path;
41  import org.apache.hadoop.hbase.Cell;
42  import org.apache.hadoop.hbase.CellUtil;
43  import org.apache.hadoop.hbase.HBaseConfiguration;
44  import org.apache.hadoop.hbase.HBaseInterfaceAudience;
45  import org.apache.hadoop.hbase.Tag;
46  import org.apache.hadoop.hbase.util.Bytes;
47  import org.apache.hadoop.hbase.util.FSUtils;
48  import org.codehaus.jackson.map.ObjectMapper;
49  
50  import org.apache.hadoop.hbase.regionserver.wal.ProtobufLogReader;
51  // imports for things that haven't moved yet.
52  import org.apache.hadoop.hbase.regionserver.wal.WALEdit;
53  
54  /**
55   * WALPrettyPrinter prints the contents of a given WAL with a variety of
56   * options affecting formatting and extent of content.
57   *
58   * It targets two usage cases: pretty printing for ease of debugging directly by
59   * humans, and JSON output for consumption by monitoring and/or maintenance
60   * scripts.
61   *
62   * It can filter by row, region, or sequence id.
63   *
64   * It can also toggle output of values.
65   *
66   */
67  @InterfaceAudience.LimitedPrivate(HBaseInterfaceAudience.TOOLS)
68  @InterfaceStability.Evolving
69  public class WALPrettyPrinter {
70    private boolean outputValues;
71    private boolean outputJSON;
72    // The following enable filtering by sequence, region, and row, respectively
73    private long sequence;
74    private String region;
75    private String row;
76    // enable in order to output a single list of transactions from several files
77    private boolean persistentOutput;
78    private boolean firstTxn;
79    // useful for programmatic capture of JSON output
80    private PrintStream out;
81    // for JSON encoding
82    private static final ObjectMapper MAPPER = new ObjectMapper();
83  
84    /**
85     * Basic constructor that simply initializes values to reasonable defaults.
86     */
87    public WALPrettyPrinter() {
88      outputValues = false;
89      outputJSON = false;
90      sequence = -1;
91      region = null;
92      row = null;
93      persistentOutput = false;
94      firstTxn = true;
95      out = System.out;
96    }
97  
98    /**
99     * Fully specified constructor.
100    *
101    * @param outputValues
102    *          when true, enables output of values along with other log
103    *          information
104    * @param outputJSON
105    *          when true, enables output in JSON format rather than a
106    *          "pretty string"
107    * @param sequence
108    *          when nonnegative, serves as a filter; only log entries with this
109    *          sequence id will be printed
110    * @param region
111    *          when not null, serves as a filter; only log entries from this
112    *          region will be printed
113    * @param row
114    *          when not null, serves as a filter; only log entries from this row
115    *          will be printed
116    * @param persistentOutput
117    *          keeps a single list running for multiple files. if enabled, the
118    *          endPersistentOutput() method must be used!
119    * @param out
120    *          Specifies an alternative to stdout for the destination of this
121    *          PrettyPrinter's output.
122    */
123   public WALPrettyPrinter(boolean outputValues, boolean outputJSON,
124       long sequence, String region, String row, boolean persistentOutput,
125       PrintStream out) {
126     this.outputValues = outputValues;
127     this.outputJSON = outputJSON;
128     this.sequence = sequence;
129     this.region = region;
130     this.row = row;
131     this.persistentOutput = persistentOutput;
132     if (persistentOutput) {
133       beginPersistentOutput();
134     }
135     this.out = out;
136     this.firstTxn = true;
137   }
138 
139   /**
140    * turns value output on
141    */
142   public void enableValues() {
143     outputValues = true;
144   }
145 
146   /**
147    * turns value output off
148    */
149   public void disableValues() {
150     outputValues = false;
151   }
152 
153   /**
154    * turns JSON output on
155    */
156   public void enableJSON() {
157     outputJSON = true;
158   }
159 
160   /**
161    * turns JSON output off, and turns on "pretty strings" for human consumption
162    */
163   public void disableJSON() {
164     outputJSON = false;
165   }
166 
167   /**
168    * sets the region by which output will be filtered
169    *
170    * @param sequence
171    *          when nonnegative, serves as a filter; only log entries with this
172    *          sequence id will be printed
173    */
174   public void setSequenceFilter(long sequence) {
175     this.sequence = sequence;
176   }
177 
178   /**
179    * sets the region by which output will be filtered
180    *
181    * @param region
182    *          when not null, serves as a filter; only log entries from this
183    *          region will be printed
184    */
185   public void setRegionFilter(String region) {
186     this.region = region;
187   }
188 
189   /**
190    * sets the region by which output will be filtered
191    *
192    * @param row
193    *          when not null, serves as a filter; only log entries from this row
194    *          will be printed
195    */
196   public void setRowFilter(String row) {
197     this.row = row;
198   }
199 
200   /**
201    * enables output as a single, persistent list. at present, only relevant in
202    * the case of JSON output.
203    */
204   public void beginPersistentOutput() {
205     if (persistentOutput)
206       return;
207     persistentOutput = true;
208     firstTxn = true;
209     if (outputJSON)
210       out.print("[");
211   }
212 
213   /**
214    * ends output of a single, persistent list. at present, only relevant in the
215    * case of JSON output.
216    */
217   public void endPersistentOutput() {
218     if (!persistentOutput)
219       return;
220     persistentOutput = false;
221     if (outputJSON)
222       out.print("]");
223   }
224 
225   /**
226    * reads a log file and outputs its contents, one transaction at a time, as
227    * specified by the currently configured options
228    *
229    * @param conf
230    *          the HBase configuration relevant to this log file
231    * @param p
232    *          the path of the log file to be read
233    * @throws IOException
234    *           may be unable to access the configured filesystem or requested
235    *           file.
236    */
237   public void processFile(final Configuration conf, final Path p)
238       throws IOException {
239     FileSystem fs = p.getFileSystem(conf);
240     if (!fs.exists(p)) {
241       throw new FileNotFoundException(p.toString());
242     }
243     if (!fs.isFile(p)) {
244       throw new IOException(p + " is not a file");
245     }
246 
247     WAL.Reader log = WALFactory.createReader(fs, p, conf);
248     
249     if (log instanceof ProtobufLogReader) {
250       List<String> writerClsNames = ((ProtobufLogReader) log).getWriterClsNames();
251       if (writerClsNames != null && writerClsNames.size() > 0) {
252         out.print("Writer Classes: ");
253         for (int i = 0; i < writerClsNames.size(); i++) {
254           out.print(writerClsNames.get(i));
255           if (i != writerClsNames.size() - 1) {
256             out.print(" ");
257           }
258         }
259         out.println();
260       }
261       
262       String cellCodecClsName = ((ProtobufLogReader) log).getCodecClsName();
263       if (cellCodecClsName != null) {
264         out.println("Cell Codec Class: " + cellCodecClsName);
265       }
266     }
267     
268     if (outputJSON && !persistentOutput) {
269       out.print("[");
270       firstTxn = true;
271     }
272     
273     try {
274       WAL.Entry entry;
275       while ((entry = log.next()) != null) {
276         WALKey key = entry.getKey();
277         WALEdit edit = entry.getEdit();
278         // begin building a transaction structure
279         Map<String, Object> txn = key.toStringMap();
280         long writeTime = key.getWriteTime();
281         // check output filters
282         if (sequence >= 0 && ((Long) txn.get("sequence")) != sequence)
283           continue;
284         if (region != null && !((String) txn.get("region")).equals(region))
285           continue;
286         // initialize list into which we will store atomic actions
287         List<Map> actions = new ArrayList<Map>();
288         for (Cell cell : edit.getCells()) {
289           // add atomic operation to txn
290           Map<String, Object> op = new HashMap<String, Object>(toStringMap(cell));
291           if (outputValues) op.put("value", Bytes.toStringBinary(cell.getValue()));
292           // check row output filter
293           if (row == null || ((String) op.get("row")).equals(row)) {
294             actions.add(op);
295           }
296         }
297         if (actions.size() == 0)
298           continue;
299         txn.put("actions", actions);
300         if (outputJSON) {
301           // JSON output is a straightforward "toString" on the txn object
302           if (firstTxn)
303             firstTxn = false;
304           else
305             out.print(",");
306           // encode and print JSON
307           out.print(MAPPER.writeValueAsString(txn));
308         } else {
309           // Pretty output, complete with indentation by atomic action
310           out.println("Sequence=" + txn.get("sequence") + " "
311               + ", region=" + txn.get("region") + " at write timestamp=" + new Date(writeTime));
312           for (int i = 0; i < actions.size(); i++) {
313             Map op = actions.get(i);
314             out.println("row=" + op.get("row") +
315                 ", column=" + op.get("family") + ":" + op.get("qualifier"));
316             if (op.get("tag") != null) {
317               out.println("    tag: " + op.get("tag"));
318             }
319             if (outputValues) out.println("    value: " + op.get("value"));
320           }
321         }
322       }
323     } finally {
324       log.close();
325     }
326     if (outputJSON && !persistentOutput) {
327       out.print("]");
328     }
329   }
330 
331   private static Map<String, Object> toStringMap(Cell cell) {
332     Map<String, Object> stringMap = new HashMap<String, Object>();
333     stringMap.put("row",
334         Bytes.toStringBinary(cell.getRowArray(), cell.getRowOffset(), cell.getRowLength()));
335     stringMap.put("family", Bytes.toStringBinary(cell.getFamilyArray(), cell.getFamilyOffset(),
336                 cell.getFamilyLength()));
337     stringMap.put("qualifier",
338         Bytes.toStringBinary(cell.getQualifierArray(), cell.getQualifierOffset(),
339             cell.getQualifierLength()));
340     stringMap.put("timestamp", cell.getTimestamp());
341     stringMap.put("vlen", cell.getValueLength());
342     if (cell.getTagsLength() > 0) {
343       List<String> tagsString = new ArrayList<String>();
344       Iterator<Tag> tagsIterator = CellUtil.tagsIterator(cell.getTagsArray(), cell.getTagsOffset(),
345           cell.getTagsLength());
346       while (tagsIterator.hasNext()) {
347         Tag tag = tagsIterator.next();
348         tagsString.add((tag.getType()) + ":"
349             + Bytes.toStringBinary(tag.getBuffer(), tag.getTagOffset(), tag.getTagLength()));
350       }
351       stringMap.put("tag", tagsString);
352     }
353     return stringMap;
354   }
355 
356   public static void main(String[] args) throws IOException {
357     run(args);
358   }
359 
360   /**
361    * Pass one or more log file names and formatting options and it will dump out
362    * a text version of the contents on <code>stdout</code>.
363    *
364    * @param args
365    *          Command line arguments
366    * @throws IOException
367    *           Thrown upon file system errors etc.
368    */
369   public static void run(String[] args) throws IOException {
370     // create options
371     Options options = new Options();
372     options.addOption("h", "help", false, "Output help message");
373     options.addOption("j", "json", false, "Output JSON");
374     options.addOption("p", "printvals", false, "Print values");
375     options.addOption("r", "region", true,
376         "Region to filter by. Pass encoded region name; e.g. '9192caead6a5a20acb4454ffbc79fa14'");
377     options.addOption("s", "sequence", true,
378         "Sequence to filter by. Pass sequence number.");
379     options.addOption("w", "row", true, "Row to filter by. Pass row name.");
380 
381     WALPrettyPrinter printer = new WALPrettyPrinter();
382     CommandLineParser parser = new PosixParser();
383     List<?> files = null;
384     try {
385       CommandLine cmd = parser.parse(options, args);
386       files = cmd.getArgList();
387       if (files.size() == 0 || cmd.hasOption("h")) {
388         HelpFormatter formatter = new HelpFormatter();
389         formatter.printHelp("WAL <filename...>", options, true);
390         System.exit(-1);
391       }
392       // configure the pretty printer using command line options
393       if (cmd.hasOption("p"))
394         printer.enableValues();
395       if (cmd.hasOption("j"))
396         printer.enableJSON();
397       if (cmd.hasOption("r"))
398         printer.setRegionFilter(cmd.getOptionValue("r"));
399       if (cmd.hasOption("s"))
400         printer.setSequenceFilter(Long.parseLong(cmd.getOptionValue("s")));
401       if (cmd.hasOption("w"))
402         printer.setRowFilter(cmd.getOptionValue("w"));
403     } catch (ParseException e) {
404       e.printStackTrace();
405       HelpFormatter formatter = new HelpFormatter();
406       formatter.printHelp("HFile filename(s) ", options, true);
407       System.exit(-1);
408     }
409     // get configuration, file system, and process the given files
410     Configuration conf = HBaseConfiguration.create();
411     FSUtils.setFsDefault(conf, FSUtils.getRootDir(conf));
412 
413     // begin output
414     printer.beginPersistentOutput();
415     for (Object f : files) {
416       Path file = new Path((String) f);
417       FileSystem fs = file.getFileSystem(conf);
418       if (!fs.exists(file)) {
419         System.err.println("ERROR, file doesnt exist: " + file);
420         return;
421       }
422       printer.processFile(conf, file);
423     }
424     printer.endPersistentOutput();
425   }
426 }