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  
19  package org.apache.hadoop.hbase.regionserver.metrics;
20  
21  import org.apache.commons.logging.Log;
22  import org.apache.commons.logging.LogFactory;
23  import org.apache.hadoop.conf.Configuration;
24  import org.apache.hadoop.fs.Path;
25  import org.apache.hadoop.hbase.io.HeapSize;
26  import org.apache.hadoop.hbase.io.hfile.HFile;
27  import org.apache.hadoop.hbase.regionserver.HRegion;
28  import org.apache.hadoop.hbase.regionserver.metrics.SchemaMetrics.SchemaAware;
29  import org.apache.hadoop.hbase.util.ClassSize;
30  
31  /**
32   * A base class for objects that are associated with a particular table and
33   * column family. Provides a way to obtain the schema metrics object.
34   * <p>
35   * Due to the variety of things that can be associated with a table/CF, there
36   * are many ways to initialize this base class, either in the constructor, or
37   * from another similar object. For example, an HFile reader configures HFile
38   * blocks it reads with its own table/CF name.
39   */
40  public class SchemaConfigured implements HeapSize, SchemaAware {
41    private static final Log LOG = LogFactory.getLog(SchemaConfigured.class);
42  
43    // These are not final because we set them at runtime e.g. for HFile blocks.
44    private String cfName;
45    private String tableName;
46  
47    /**
48     * Schema metrics. Can only be initialized when we know our column family
49     * name, table name, and have had a chance to take a look at the
50     * configuration (in {@link SchemaMetrics#configureGlobally(Configuration))
51     * so we know whether we are using per-table metrics. Therefore, initialized
52     * lazily. We don't make this volatile because even if a thread sees a stale
53     * value of null, it will be re-initialized to the same value that other
54     * threads see.
55     */
56    private SchemaMetrics schemaMetrics;
57  
58    static {
59      if (ClassSize.OBJECT <= 0 || ClassSize.REFERENCE <= 0) {
60        throw new AssertionError("Class sizes are not initialized");
61      }
62    }
63  
64    /**
65     * Estimated heap size of this object. We don't count table name and column
66     * family name characters because these strings are shared among many
67     * objects. We need unaligned size to reuse this in subclasses.
68     */
69    public static final int SCHEMA_CONFIGURED_UNALIGNED_HEAP_SIZE =
70        ClassSize.OBJECT + 3 * ClassSize.REFERENCE;
71  
72    private static final int SCHEMA_CONFIGURED_ALIGNED_HEAP_SIZE =
73        ClassSize.align(SCHEMA_CONFIGURED_UNALIGNED_HEAP_SIZE);
74  
75    /** A helper constructor that configures the "use table name" flag. */
76    private SchemaConfigured(Configuration conf) {
77      SchemaMetrics.configureGlobally(conf);
78      // Even though we now know if table-level metrics are used, we can't
79      // initialize schemaMetrics yet, because CF and table name are only known
80      // to the calling constructor.
81    }
82  
83    /**
84     * Creates an instance corresponding to an unknown table and column family.
85     * Used in unit tests. 
86     */
87    public static SchemaConfigured createUnknown() {
88      return new SchemaConfigured(null, SchemaMetrics.UNKNOWN,
89          SchemaMetrics.UNKNOWN);
90    }
91  
92    /**
93     * Default constructor. Only use when column/family name are not known at
94     * construction (i.e. for HFile blocks).
95     */
96    public SchemaConfigured() {
97    }
98  
99    /**
100    * Initialize table and column family name from an HFile path. If
101    * configuration is null,
102    * {@link SchemaMetrics#configureGlobally(Configuration)} should have been
103    * called already.
104    */
105   public SchemaConfigured(Configuration conf, Path path) {
106     this(conf);
107 
108     if (path != null) {
109       String splits[] = path.toString().split("/");
110       int numPathLevels = splits.length;
111       if (numPathLevels > 0 && splits[0].isEmpty()) {
112         // This path starts with an '/'.
113         --numPathLevels;
114       }
115       if (numPathLevels < HFile.MIN_NUM_HFILE_PATH_LEVELS) {
116         LOG.warn("Could not determine table and column family of the HFile "
117             + "path " + path + ". Expecting at least "
118             + HFile.MIN_NUM_HFILE_PATH_LEVELS + " path components.");
119         path = null;
120       } else {
121         cfName = splits[splits.length - 2];
122         if (cfName.equals(HRegion.REGION_TEMP_SUBDIR)) {
123           // This is probably a compaction or flush output file. We will set
124           // the real CF name later.
125           cfName = null;
126         }
127         tableName = splits[splits.length - 4];
128         return;
129       }
130     }
131 
132     // This might also happen if we were passed an incorrect path.
133     cfName = SchemaMetrics.UNKNOWN;
134     tableName = SchemaMetrics.UNKNOWN;
135   }
136 
137   /**
138    * Used when we know an HFile path to deduce table and CF name from, but do
139    * not have a configuration.
140    * @param path an HFile path
141    */
142   public SchemaConfigured(Path path) {
143     this(null, path);
144   }
145 
146   /**
147    * Used when we know table and column family name. If configuration is null,
148    * {@link SchemaMetrics#configureGlobally(Configuration)} should have been
149    * called already.
150    */
151   public SchemaConfigured(Configuration conf, String tableName, String cfName)
152   {
153     this(conf);
154     this.tableName = tableName;
155     this.cfName = cfName;
156   }
157 
158   public SchemaConfigured(SchemaAware that) {
159     tableName = that.getTableName();
160     cfName = that.getColumnFamilyName();
161     schemaMetrics = that.getSchemaMetrics();
162   }
163 
164   @Override
165   public String getTableName() {
166     return tableName;
167   }
168 
169   @Override
170   public String getColumnFamilyName() {
171     return cfName;
172   }
173 
174   @Override
175   public SchemaMetrics getSchemaMetrics() {
176     if (schemaMetrics == null) {
177       if (tableName == null || cfName == null) {
178         throw new IllegalStateException("Schema metrics requested before " +
179             "table/CF name initialization: " + schemaConfAsJSON());
180       }
181       schemaMetrics = SchemaMetrics.getInstance(tableName, cfName);
182     }
183     return schemaMetrics;
184   }
185 
186   /**
187    * Configures the given object (e.g. an HFile block) with the current table
188    * and column family name, and the associated collection of metrics. Please
189    * note that this method configures the <b>other</b> object, not <b>this</b>
190    * object.
191    */
192   public void passSchemaMetricsTo(SchemaConfigured target) {
193     if (isNull()) {
194       resetSchemaMetricsConf(target);
195       return;
196     }
197 
198     if (!isSchemaConfigured()) {
199       // Cannot configure another object if we are not configured ourselves.
200       throw new IllegalStateException("Table name/CF not initialized: " +
201           schemaConfAsJSON());
202     }
203 
204     if (conflictingWith(target)) {
205       // Make sure we don't try to change table or CF name.
206       throw new IllegalArgumentException("Trying to change table name to \"" +
207           tableName + "\", CF name to \"" + cfName + "\" from " +
208           target.schemaConfAsJSON());
209     }
210 
211     target.tableName = tableName;
212     target.cfName = cfName;
213     target.schemaMetrics = schemaMetrics;
214     target.schemaConfigurationChanged();
215   }
216 
217   /**
218    * Reset schema metrics configuration in this particular instance. Used when
219    * legitimately need to re-initialize the object with another table/CF.
220    * This is a static method because its use is discouraged and reserved for
221    * when it is really necessary (e.g. writing HFiles in a temp direcdtory
222    * on compaction).
223    */
224   public static void resetSchemaMetricsConf(SchemaConfigured target) {
225     target.tableName = null;
226     target.cfName = null;
227     target.schemaMetrics = null;
228     target.schemaConfigurationChanged();
229   }
230 
231   @Override
232   public long heapSize() {
233     return SCHEMA_CONFIGURED_ALIGNED_HEAP_SIZE;
234   }
235 
236   public String schemaConfAsJSON() {
237     return "{\"tableName\":\"" + tableName + "\",\"cfName\":\"" + cfName
238         + "\"}";
239   }
240 
241   protected boolean isSchemaConfigured() {
242     return tableName != null && cfName != null;
243   }
244 
245   private boolean isNull() {
246     return tableName == null && cfName == null && schemaMetrics == null;
247   }
248 
249   /**
250    * Determines if the current object's table/CF settings are not in conflict
251    * with the other object's table and CF. If the other object's table/CF are
252    * undefined, they are not considered to be in conflict. Used to sanity-check
253    * configuring the other object with this object's table/CF.
254    */
255   boolean conflictingWith(SchemaConfigured other) {
256     return (other.tableName != null && !tableName.equals(other.tableName)) ||
257         (other.cfName != null && !cfName.equals(other.cfName));
258   }
259 
260   /**
261    * A hook method called when schema configuration changes. Can be used to
262    * update schema-aware member fields.
263    */
264   protected void schemaConfigurationChanged() {
265   }
266 
267 }