/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.apache.hadoop.hbase.regionserver.metrics; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.Path; import org.apache.hadoop.hbase.io.HeapSize; import org.apache.hadoop.hbase.io.hfile.HFile; import org.apache.hadoop.hbase.regionserver.HRegion; import org.apache.hadoop.hbase.regionserver.metrics.SchemaMetrics.SchemaAware; import org.apache.hadoop.hbase.util.ClassSize; /** * A base class for objects that are associated with a particular table and * column family. Provides a way to obtain the schema metrics object. * <p> * Due to the variety of things that can be associated with a table/CF, there * are many ways to initialize this base class, either in the constructor, or * from another similar object. For example, an HFile reader configures HFile * blocks it reads with its own table/CF name. */ public class SchemaConfigured implements HeapSize, SchemaAware { private static final Log LOG = LogFactory.getLog(SchemaConfigured.class); // These are not final because we set them at runtime e.g. for HFile blocks. private String cfName; private String tableName; /** * Schema metrics. Can only be initialized when we know our column family * name, table name, and have had a chance to take a look at the * configuration (in {@link SchemaMetrics#configureGlobally(Configuration)) * so we know whether we are using per-table metrics. Therefore, initialized * lazily. We don't make this volatile because even if a thread sees a stale * value of null, it will be re-initialized to the same value that other * threads see. */ private SchemaMetrics schemaMetrics; static { if (ClassSize.OBJECT <= 0 || ClassSize.REFERENCE <= 0) { throw new AssertionError("Class sizes are not initialized"); } } /** * Estimated heap size of this object. We don't count table name and column * family name characters because these strings are shared among many * objects. We need unaligned size to reuse this in subclasses. */ public static final int SCHEMA_CONFIGURED_UNALIGNED_HEAP_SIZE = ClassSize.OBJECT + 3 * ClassSize.REFERENCE; private static final int SCHEMA_CONFIGURED_ALIGNED_HEAP_SIZE = ClassSize.align(SCHEMA_CONFIGURED_UNALIGNED_HEAP_SIZE); /** A helper constructor that configures the "use table name" flag. */ private SchemaConfigured(Configuration conf) { SchemaMetrics.configureGlobally(conf); // Even though we now know if table-level metrics are used, we can't // initialize schemaMetrics yet, because CF and table name are only known // to the calling constructor. } /** * Creates an instance corresponding to an unknown table and column family. * Used in unit tests. */ public static SchemaConfigured createUnknown() { return new SchemaConfigured(null, SchemaMetrics.UNKNOWN, SchemaMetrics.UNKNOWN); } /** * Default constructor. Only use when column/family name are not known at * construction (i.e. for HFile blocks). */ public SchemaConfigured() { } /** * Initialize table and column family name from an HFile path. If * configuration is null, * {@link SchemaMetrics#configureGlobally(Configuration)} should have been * called already. */ public SchemaConfigured(Configuration conf, Path path) { this(conf); if (path != null) { String splits[] = path.toString().split("/"); int numPathLevels = splits.length; if (numPathLevels > 0 && splits[0].isEmpty()) { // This path starts with an '/'. --numPathLevels; } if (numPathLevels < HFile.MIN_NUM_HFILE_PATH_LEVELS) { LOG.warn("Could not determine table and column family of the HFile " + "path " + path + ". Expecting at least " + HFile.MIN_NUM_HFILE_PATH_LEVELS + " path components."); path = null; } else { cfName = splits[splits.length - 2]; if (cfName.equals(HRegion.REGION_TEMP_SUBDIR)) { // This is probably a compaction or flush output file. We will set // the real CF name later. cfName = null; } else { cfName = cfName.intern(); } tableName = splits[splits.length - 4].intern(); return; } } // This might also happen if we were passed an incorrect path. cfName = SchemaMetrics.UNKNOWN; tableName = SchemaMetrics.UNKNOWN; } /** * Used when we know an HFile path to deduce table and CF name from, but do * not have a configuration. * @param path an HFile path */ public SchemaConfigured(Path path) { this(null, path); } /** * Used when we know table and column family name. If configuration is null, * {@link SchemaMetrics#configureGlobally(Configuration)} should have been * called already. */ public SchemaConfigured(Configuration conf, String tableName, String cfName) { this(conf); this.tableName = tableName != null ? tableName.intern() : tableName; this.cfName = cfName != null ? cfName.intern() : cfName; } public SchemaConfigured(SchemaAware that) { tableName = that.getTableName().intern(); cfName = that.getColumnFamilyName().intern(); schemaMetrics = that.getSchemaMetrics(); } @Override public String getTableName() { return tableName; } @Override public String getColumnFamilyName() { return cfName; } @Override public SchemaMetrics getSchemaMetrics() { if (schemaMetrics == null) { if (tableName == null || cfName == null) { throw new IllegalStateException("Schema metrics requested before " + "table/CF name initialization: " + schemaConfAsJSON()); } schemaMetrics = SchemaMetrics.getInstance(tableName, cfName); } return schemaMetrics; } /** * Configures the given object (e.g. an HFile block) with the current table * and column family name, and the associated collection of metrics. Please * note that this method configures the <b>other</b> object, not <b>this</b> * object. */ public void passSchemaMetricsTo(SchemaConfigured target) { if (isNull()) { resetSchemaMetricsConf(target); return; } if (!isSchemaConfigured()) { // Cannot configure another object if we are not configured ourselves. throw new IllegalStateException("Table name/CF not initialized: " + schemaConfAsJSON()); } if (conflictingWith(target)) { // Make sure we don't try to change table or CF name. throw new IllegalArgumentException("Trying to change table name to \"" + tableName + "\", CF name to \"" + cfName + "\" from " + target.schemaConfAsJSON()); } target.tableName = tableName.intern(); target.cfName = cfName.intern(); target.schemaMetrics = schemaMetrics; target.schemaConfigurationChanged(); } /** * Reset schema metrics configuration in this particular instance. Used when * legitimately need to re-initialize the object with another table/CF. * This is a static method because its use is discouraged and reserved for * when it is really necessary (e.g. writing HFiles in a temp direcdtory * on compaction). */ public static void resetSchemaMetricsConf(SchemaConfigured target) { target.tableName = null; target.cfName = null; target.schemaMetrics = null; target.schemaConfigurationChanged(); } @Override public long heapSize() { return SCHEMA_CONFIGURED_ALIGNED_HEAP_SIZE; } public String schemaConfAsJSON() { return "{\"tableName\":\"" + tableName + "\",\"cfName\":\"" + cfName + "\"}"; } protected boolean isSchemaConfigured() { return tableName != null && cfName != null; } private boolean isNull() { return tableName == null && cfName == null && schemaMetrics == null; } /** * Determines if the current object's table/CF settings are not in conflict * with the other object's table and CF. If the other object's table/CF are * undefined, they are not considered to be in conflict. Used to sanity-check * configuring the other object with this object's table/CF. */ boolean conflictingWith(SchemaConfigured other) { return (other.tableName != null && !tableName.equals(other.tableName)) || (other.cfName != null && !cfName.equals(other.cfName)); } /** * A hook method called when schema configuration changes. Can be used to * update schema-aware member fields. */ protected void schemaConfigurationChanged() { } }