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         } else {
127           cfName = cfName.intern();
128         }
129         tableName = splits[splits.length - 4].intern();
130         return;
131       }
132     }
133 
134     // This might also happen if we were passed an incorrect path.
135     cfName = SchemaMetrics.UNKNOWN;
136     tableName = SchemaMetrics.UNKNOWN;
137   }
138 
139   /**
140    * Used when we know an HFile path to deduce table and CF name from, but do
141    * not have a configuration.
142    * @param path an HFile path
143    */
144   public SchemaConfigured(Path path) {
145     this(null, path);
146   }
147 
148   /**
149    * Used when we know table and column family name. If configuration is null,
150    * {@link SchemaMetrics#configureGlobally(Configuration)} should have been
151    * called already.
152    */
153   public SchemaConfigured(Configuration conf, String tableName, String cfName)
154   {
155     this(conf);
156     this.tableName = tableName != null ? tableName.intern() : tableName;
157     this.cfName = cfName != null ? cfName.intern() : cfName;
158   }
159 
160   public SchemaConfigured(SchemaAware that) {
161     tableName = that.getTableName().intern();
162     cfName = that.getColumnFamilyName().intern();
163     schemaMetrics = that.getSchemaMetrics();
164   }
165 
166   @Override
167   public String getTableName() {
168     return tableName;
169   }
170 
171   @Override
172   public String getColumnFamilyName() {
173     return cfName;
174   }
175 
176   @Override
177   public SchemaMetrics getSchemaMetrics() {
178     if (schemaMetrics == null) {
179       if (tableName == null || cfName == null) {
180         throw new IllegalStateException("Schema metrics requested before " +
181             "table/CF name initialization: " + schemaConfAsJSON());
182       }
183       schemaMetrics = SchemaMetrics.getInstance(tableName, cfName);
184     }
185     return schemaMetrics;
186   }
187 
188   /**
189    * Configures the given object (e.g. an HFile block) with the current table
190    * and column family name, and the associated collection of metrics. Please
191    * note that this method configures the <b>other</b> object, not <b>this</b>
192    * object.
193    */
194   public void passSchemaMetricsTo(SchemaConfigured target) {
195     if (isNull()) {
196       resetSchemaMetricsConf(target);
197       return;
198     }
199 
200     if (!isSchemaConfigured()) {
201       // Cannot configure another object if we are not configured ourselves.
202       throw new IllegalStateException("Table name/CF not initialized: " +
203           schemaConfAsJSON());
204     }
205 
206     if (conflictingWith(target)) {
207       // Make sure we don't try to change table or CF name.
208       throw new IllegalArgumentException("Trying to change table name to \"" +
209           tableName + "\", CF name to \"" + cfName + "\" from " +
210           target.schemaConfAsJSON());
211     }
212 
213     target.tableName = tableName.intern();
214     target.cfName = cfName.intern();
215     target.schemaMetrics = schemaMetrics;
216     target.schemaConfigurationChanged();
217   }
218 
219   /**
220    * Reset schema metrics configuration in this particular instance. Used when
221    * legitimately need to re-initialize the object with another table/CF.
222    * This is a static method because its use is discouraged and reserved for
223    * when it is really necessary (e.g. writing HFiles in a temp direcdtory
224    * on compaction).
225    */
226   public static void resetSchemaMetricsConf(SchemaConfigured target) {
227     target.tableName = null;
228     target.cfName = null;
229     target.schemaMetrics = null;
230     target.schemaConfigurationChanged();
231   }
232 
233   @Override
234   public long heapSize() {
235     return SCHEMA_CONFIGURED_ALIGNED_HEAP_SIZE;
236   }
237 
238   public String schemaConfAsJSON() {
239     return "{\"tableName\":\"" + tableName + "\",\"cfName\":\"" + cfName
240         + "\"}";
241   }
242 
243   protected boolean isSchemaConfigured() {
244     return tableName != null && cfName != null;
245   }
246 
247   private boolean isNull() {
248     return tableName == null && cfName == null && schemaMetrics == null;
249   }
250 
251   /**
252    * Determines if the current object's table/CF settings are not in conflict
253    * with the other object's table and CF. If the other object's table/CF are
254    * undefined, they are not considered to be in conflict. Used to sanity-check
255    * configuring the other object with this object's table/CF.
256    */
257   boolean conflictingWith(SchemaConfigured other) {
258     return (other.tableName != null && !tableName.equals(other.tableName)) ||
259         (other.cfName != null && !cfName.equals(other.cfName));
260   }
261 
262   /**
263    * A hook method called when schema configuration changes. Can be used to
264    * update schema-aware member fields.
265    */
266   protected void schemaConfigurationChanged() {
267   }
268 
269 }