Author: stack
Date: Wed Aug 10 22:16:30 2011
New Revision: 1156390

URL: http://svn.apache.org/viewvc?rev=1156390&view=rev
Log:
HBASE-4114 Metrics for HFile HDFS block locality

Added:
    
hbase/trunk/src/main/java/org/apache/hadoop/hbase/HDFSBlocksDistribution.java
Modified:
    hbase/trunk/CHANGES.txt
    hbase/trunk/src/main/java/org/apache/hadoop/hbase/master/HMaster.java
    hbase/trunk/src/main/java/org/apache/hadoop/hbase/master/LoadBalancer.java
    hbase/trunk/src/main/java/org/apache/hadoop/hbase/regionserver/HRegion.java
    
hbase/trunk/src/main/java/org/apache/hadoop/hbase/regionserver/HRegionServer.java
    
hbase/trunk/src/main/java/org/apache/hadoop/hbase/regionserver/StoreFile.java
    
hbase/trunk/src/main/java/org/apache/hadoop/hbase/regionserver/metrics/RegionServerMetrics.java
    hbase/trunk/src/main/java/org/apache/hadoop/hbase/util/FSUtils.java
    hbase/trunk/src/test/java/org/apache/hadoop/hbase/HBaseTestingUtility.java
    
hbase/trunk/src/test/java/org/apache/hadoop/hbase/regionserver/TestHRegion.java
    hbase/trunk/src/test/java/org/apache/hadoop/hbase/util/TestFSUtils.java

Modified: hbase/trunk/CHANGES.txt
URL: 
http://svn.apache.org/viewvc/hbase/trunk/CHANGES.txt?rev=1156390&r1=1156389&r2=1156390&view=diff
==============================================================================
--- hbase/trunk/CHANGES.txt (original)
+++ hbase/trunk/CHANGES.txt Wed Aug 10 22:16:30 2011
@@ -421,6 +421,7 @@ Release 0.91.0 - Unreleased
                (Nichole Treadway and Nicholas Telford)
    HBASE-2233  Support both Hadoop 0.20 and 0.22
    HBASE-3857  Change the HFile Format (Mikhail & Liyin)
+   HBASE-4114  Metrics for HFile HDFS block locality (Ming Ma)
 
 Release 0.90.5 - Unreleased
 

Added: 
hbase/trunk/src/main/java/org/apache/hadoop/hbase/HDFSBlocksDistribution.java
URL: 
http://svn.apache.org/viewvc/hbase/trunk/src/main/java/org/apache/hadoop/hbase/HDFSBlocksDistribution.java?rev=1156390&view=auto
==============================================================================
--- 
hbase/trunk/src/main/java/org/apache/hadoop/hbase/HDFSBlocksDistribution.java 
(added)
+++ 
hbase/trunk/src/main/java/org/apache/hadoop/hbase/HDFSBlocksDistribution.java 
Wed Aug 10 22:16:30 2011
@@ -0,0 +1,234 @@
+/**
+ * Copyright 2011 The Apache Software Foundation
+ *
+ * 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;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.NavigableSet;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+
+/**
+ * Data structure to describe the distribution of HDFS blocks amount hosts
+ */
+public class HDFSBlocksDistribution {
+  private Map<String,HostAndWeight> hostAndWeights = null;
+  private long uniqueBlocksTotalWeight = 0;
+    
+  /**
+   * Stores the hostname and weight for that hostname.
+   *
+   * This is used when determining the physical locations of the blocks making
+   * up a region.
+   *
+   * To make a prioritized list of the hosts holding the most data of a region,
+   * this class is used to count the total weight for each host.  The weight is
+   * currently just the size of the file.
+   */
+  public static class HostAndWeight {
+
+    private String host;
+    private long weight;
+
+    /**
+     * Constructor
+     * @param host the host name
+     * @param weight the weight
+     */    
+    public HostAndWeight(String host, long weight) {
+      this.host = host;
+      this.weight = weight;
+    }
+
+    /**
+     * add weight
+     * @param weight the weight
+     */        
+    public void addWeight(long weight) {
+      this.weight += weight;
+    }
+
+    /**
+     * @return the host name
+     */            
+    public String getHost() {
+      return host;
+    }
+
+    /**
+     * @return the weight
+     */                
+    public long getWeight() {
+      return weight;
+    }
+
+    /**
+     * comparator used to sort hosts based on weight
+     */                
+    public static class WeightComparator implements Comparator<HostAndWeight> {
+      @Override
+      public int compare(HostAndWeight l, HostAndWeight r) {
+        if(l.getWeight() == r.getWeight()) {
+          return l.getHost().compareTo(r.getHost());
+        }
+        return l.getWeight() < r.getWeight() ? -1 : 1;
+      }
+    }
+  }
+  
+  /**
+   * Constructor
+   */
+  public HDFSBlocksDistribution() {
+    this.hostAndWeights =
+      new TreeMap<String,HostAndWeight>();
+  }
+
+  /**
+   * @see java.lang.Object#toString()
+   */
+  @Override
+  public synchronized String toString() {
+    return "number of unique hosts in the disribution=" +
+      this.hostAndWeights.size();
+  }
+
+  /**
+   * add some weight to a list of hosts, update the value of unique block 
weight
+   * @param hosts the list of the host
+   * @param weight the weight
+   */
+  public void addHostsAndBlockWeight(String[] hosts, long weight) {
+    if (hosts == null || hosts.length == 0) {
+      throw new NullPointerException("empty hosts");
+    }
+    addUniqueWeight(weight);
+    for (String hostname : hosts) {
+      addHostAndBlockWeight(hostname, weight);
+    }
+  }
+
+  /**
+   * add some weight to the total unique weight
+   * @param weight the weight
+   */        
+  private void addUniqueWeight(long weight) {
+    uniqueBlocksTotalWeight += weight;
+  }
+  
+  
+  /**
+   * add some weight to a specific host
+   * @param host the host name
+   * @param weight the weight
+   */
+  private void addHostAndBlockWeight(String host, long weight) {
+    if (host == null) {
+      throw new NullPointerException("Passed hostname is null");
+    }
+
+    HostAndWeight hostAndWeight = this.hostAndWeights.get(host);
+    if(hostAndWeight == null) {
+      hostAndWeight = new HostAndWeight(host, weight);
+      this.hostAndWeights.put(host, hostAndWeight);
+    } else {
+      hostAndWeight.addWeight(weight);
+    }
+  }
+
+  /**
+   * @return the hosts and their weights
+   */
+  public Map<String,HostAndWeight> getHostAndWeights() {
+    return this.hostAndWeights;
+  }
+
+  /**
+   * return the weight for a specific host, that will be the total bytes of all
+   * blocks on the host
+   * @param host the host name
+   * @return the weight of the given host
+   */
+  public long getWeight(String host) {
+    long weight = 0;
+    if (host != null) {
+      HostAndWeight hostAndWeight = this.hostAndWeights.get(host);
+      if(hostAndWeight != null) {
+        weight = hostAndWeight.getWeight();
+      }
+    }
+    return weight;
+  }
+  
+  /**
+   * @return the sum of all unique blocks' weight
+   */
+  public long getUniqueBlocksTotalWeight() {
+    return uniqueBlocksTotalWeight;
+  }
+  
+  /**
+   * return the locality index of a given host
+   * @param host the host name
+   * @return the locality index of the given host
+   */
+  public float getBlockLocalityIndex(String host) {
+    float localityIndex = 0;
+    HostAndWeight hostAndWeight = this.hostAndWeights.get(host);
+    if (hostAndWeight != null && uniqueBlocksTotalWeight != 0) {
+      localityIndex=(float)hostAndWeight.weight/(float)uniqueBlocksTotalWeight;
+    }
+    return localityIndex;
+  }
+  
+  
+  /**
+   * This will add the distribution from input to this object
+   * @param otherBlocksDistribution the other hdfs blocks distribution
+   */
+  public void add(HDFSBlocksDistribution otherBlocksDistribution) {
+    Map<String,HostAndWeight> otherHostAndWeights =
+      otherBlocksDistribution.getHostAndWeights();
+    for (Map.Entry<String, HostAndWeight> otherHostAndWeight:
+      otherHostAndWeights.entrySet()) {
+      addHostAndBlockWeight(otherHostAndWeight.getValue().host,
+        otherHostAndWeight.getValue().weight);
+    }
+    addUniqueWeight(otherBlocksDistribution.getUniqueBlocksTotalWeight());
+  }
+  
+  /**
+   * return the sorted list of hosts in terms of their weights
+   */
+  public List<String> getTopHosts() {
+    NavigableSet<HostAndWeight> orderedHosts = new TreeSet<HostAndWeight>(
+      new HostAndWeight.WeightComparator());
+    orderedHosts.addAll(this.hostAndWeights.values());
+    List<String> topHosts = new ArrayList<String>(orderedHosts.size());
+    for(HostAndWeight haw : orderedHosts.descendingSet()) {
+      topHosts.add(haw.getHost());
+    }
+    return topHosts;
+  }
+
+}

Modified: hbase/trunk/src/main/java/org/apache/hadoop/hbase/master/HMaster.java
URL: 
http://svn.apache.org/viewvc/hbase/trunk/src/main/java/org/apache/hadoop/hbase/master/HMaster.java?rev=1156390&r1=1156389&r2=1156390&view=diff
==============================================================================
--- hbase/trunk/src/main/java/org/apache/hadoop/hbase/master/HMaster.java 
(original)
+++ hbase/trunk/src/main/java/org/apache/hadoop/hbase/master/HMaster.java Wed 
Aug 10 22:16:30 2011
@@ -466,6 +466,9 @@ implements HMasterInterface, HMasterRegi
     status.setStatus("Starting assignment manager");
     this.assignmentManager.joinCluster();
 
+    this.balancer.setClusterStatus(getClusterStatus());
+    this.balancer.setMasterServices(this);
+    
     // Start balancer and meta catalog janitor after meta and regions have
     // been assigned.
     status.setStatus("Starting balancer and catalog janitor");

Modified: 
hbase/trunk/src/main/java/org/apache/hadoop/hbase/master/LoadBalancer.java
URL: 
http://svn.apache.org/viewvc/hbase/trunk/src/main/java/org/apache/hadoop/hbase/master/LoadBalancer.java?rev=1156390&r1=1156389&r2=1156390&view=diff
==============================================================================
--- hbase/trunk/src/main/java/org/apache/hadoop/hbase/master/LoadBalancer.java 
(original)
+++ hbase/trunk/src/main/java/org/apache/hadoop/hbase/master/LoadBalancer.java 
Wed Aug 10 22:16:30 2011
@@ -19,29 +19,32 @@
  */
 package org.apache.hadoop.hbase.master;
 
+import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.NavigableMap;
-import java.util.NavigableSet;
 import java.util.Random;
 import java.util.TreeMap;
-import java.util.TreeSet;
 
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.apache.hadoop.conf.Configuration;
-import org.apache.hadoop.fs.BlockLocation;
-import org.apache.hadoop.fs.FileStatus;
 import org.apache.hadoop.fs.FileSystem;
-import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.hbase.ClusterStatus;
+import org.apache.hadoop.hbase.HDFSBlocksDistribution;
 import org.apache.hadoop.hbase.HRegionInfo;
+import org.apache.hadoop.hbase.HTableDescriptor;
 import org.apache.hadoop.hbase.ServerName;
+import org.apache.hadoop.hbase.TableExistsException;
+import org.apache.hadoop.hbase.regionserver.HRegion;
+import org.apache.hadoop.hbase.util.Bytes;
 
 import com.google.common.collect.MinMaxPriorityQueue;
 
@@ -66,11 +69,23 @@ public class LoadBalancer {
   private static final Random RANDOM = new Random(System.currentTimeMillis());
   // slop for regions
   private float slop;
+  private Configuration config;
+  private ClusterStatus status;
+  private MasterServices services;
 
   LoadBalancer(Configuration conf) {
     this.slop = conf.getFloat("hbase.regions.slop", (float) 0.2);
     if (slop < 0) slop = 0;
     else if (slop > 1) slop = 1;
+    this.config = conf;
+  }
+  
+  public void setClusterStatus(ClusterStatus st) {
+    this.status = st;
+  }
+
+  public void setMasterServices(MasterServices masterServices) {
+    this.services = masterServices;
   }
   
   /* 
@@ -600,106 +615,96 @@ public class LoadBalancer {
   }
 
   /**
-   * Find the block locations for all of the files for the specified region.
-   *
    * Returns an ordered list of hosts that are hosting the blocks for this
    * region.  The weight of each host is the sum of the block lengths of all
    * files on that host, so the first host in the list is the server which
    * holds the most bytes of the given region's HFiles.
    *
-   * TODO: Make this work.  Need to figure out how to match hadoop's hostnames
-   *       given for block locations with our HServerAddress.
-   * TODO: Use the right directory for the region
-   * TODO: Use getFileBlockLocations on the files not the directory
-   *
    * @param fs the filesystem
    * @param region region
    * @return ordered list of hosts holding blocks of the specified region
-   * @throws IOException if any filesystem errors
    */
   @SuppressWarnings("unused")
-  private List<String> getTopBlockLocations(FileSystem fs, HRegionInfo region)
-  throws IOException {
-    String encodedName = region.getEncodedName();
-    Path path = new Path("/hbase/table/" + encodedName);
-    FileStatus status = fs.getFileStatus(path);
-    BlockLocation [] blockLocations =
-      fs.getFileBlockLocations(status, 0, status.getLen());
-    Map<HostAndWeight,HostAndWeight> hostWeights =
-      new TreeMap<HostAndWeight,HostAndWeight>(new 
HostAndWeight.HostComparator());
-    for(BlockLocation bl : blockLocations) {
-      String [] hosts = bl.getHosts();
-      long len = bl.getLength();
-      for(String host : hosts) {
-        HostAndWeight haw = hostWeights.get(host);
-        if(haw == null) {
-          haw = new HostAndWeight(host, len);
-          hostWeights.put(haw, haw);
-        } else {
-          haw.addWeight(len);
-        }
-      }
-    }
-    NavigableSet<HostAndWeight> orderedHosts = new TreeSet<HostAndWeight>(
-        new HostAndWeight.WeightComparator());
-    orderedHosts.addAll(hostWeights.values());
-    List<String> topHosts = new ArrayList<String>(orderedHosts.size());
-    for(HostAndWeight haw : orderedHosts.descendingSet()) {
-      topHosts.add(haw.getHost());
+  private List<ServerName> getTopBlockLocations(FileSystem fs,
+    HRegionInfo region) {
+    List<ServerName> topServerNames = null;
+    try {
+      HTableDescriptor tableDescriptor = getTableDescriptor(
+        region.getTableName());
+      if (tableDescriptor != null) {
+        HDFSBlocksDistribution blocksDistribution =
+          HRegion.computeHDFSBlocksDistribution(config, tableDescriptor,
+          region.getEncodedName());
+        List<String> topHosts = blocksDistribution.getTopHosts();
+        topServerNames = mapHostNameToServerName(topHosts);
+      }
+    } catch (IOException ioe) {
+      LOG.debug("IOException during HDFSBlocksDistribution computation. for " +
+        "region = " + region.getEncodedName() , ioe);
     }
-    return topHosts;
+    
+    return topServerNames;
   }
 
   /**
-   * Stores the hostname and weight for that hostname.
-   *
-   * This is used when determining the physical locations of the blocks making
-   * up a region.
-   *
-   * To make a prioritized list of the hosts holding the most data of a region,
-   * this class is used to count the total weight for each host.  The weight is
-   * currently just the size of the file.
+   * return HTableDescriptor for a given tableName
+   * @param tableName the table name
+   * @return HTableDescriptor
+   * @throws IOException
    */
-  private static class HostAndWeight {
-
-    private final String host;
-    private long weight;
-
-    public HostAndWeight(String host, long weight) {
-      this.host = host;
-      this.weight = weight;
+  private HTableDescriptor getTableDescriptor(byte[] tableName)
+    throws IOException {
+    HTableDescriptor tableDescriptor = null;
+    try {
+      if ( this.services != null)
+      {
+        tableDescriptor = this.services.getTableDescriptors().
+          get(Bytes.toString(tableName));
     }
-
-    public void addWeight(long weight) {
-      this.weight += weight;
+    } catch (TableExistsException tee) {
+      LOG.debug("TableExistsException during getTableDescriptors." +
+        " Current table name = " + tableName , tee);
+    } catch (FileNotFoundException fnfe) {
+      LOG.debug("FileNotFoundException during getTableDescriptors." +
+        " Current table name = " + tableName , fnfe);
     }
 
-    public String getHost() {
-      return host;
+    return tableDescriptor;
     }
 
-    public long getWeight() {
-      return weight;
+  /**
+   * Map hostname to ServerName, The output ServerName list will have the same
+   * order as input hosts.
+   * @param hosts the list of hosts
+   * @return ServerName list
+   */  
+  private List<ServerName> mapHostNameToServerName(List<String> hosts) {
+    if ( hosts == null || status == null) {
+      return null;
     }
 
-    private static class HostComparator implements Comparator<HostAndWeight> {
-      @Override
-      public int compare(HostAndWeight l, HostAndWeight r) {
-        return l.getHost().compareTo(r.getHost());
-      }
-    }
+    List<ServerName> topServerNames = new ArrayList<ServerName>();
+    Collection<ServerName> regionServers = status.getServers();
 
-    private static class WeightComparator implements Comparator<HostAndWeight> 
{
-      @Override
-      public int compare(HostAndWeight l, HostAndWeight r) {
-        if(l.getWeight() == r.getWeight()) {
-          return l.getHost().compareTo(r.getHost());
+    // create a mapping from hostname to ServerName for fast lookup
+    HashMap<String, ServerName> hostToServerName =
+      new HashMap<String, ServerName>();
+    for (ServerName sn : regionServers) {
+      hostToServerName.put(sn.getHostname(), sn);
         }
-        return l.getWeight() < r.getWeight() ? -1 : 1;
+
+    for (String host : hosts ) {
+      ServerName sn = hostToServerName.get(host);
+      // it is possible that HDFS is up ( thus host is valid ),
+      // but RS is down ( thus sn is null )
+      if (sn != null) {
+        topServerNames.add(sn);
       }
     }
+    return topServerNames;
   }
 
+
   /**
    * Generates an immediate assignment plan to be used by a new master for
    * regions in transition that do not have an already known destination.

Modified: 
hbase/trunk/src/main/java/org/apache/hadoop/hbase/regionserver/HRegion.java
URL: 
http://svn.apache.org/viewvc/hbase/trunk/src/main/java/org/apache/hadoop/hbase/regionserver/HRegion.java?rev=1156390&r1=1156389&r2=1156390&view=diff
==============================================================================
--- hbase/trunk/src/main/java/org/apache/hadoop/hbase/regionserver/HRegion.java 
(original)
+++ hbase/trunk/src/main/java/org/apache/hadoop/hbase/regionserver/HRegion.java 
Wed Aug 10 22:16:30 2011
@@ -60,6 +60,7 @@ import org.apache.hadoop.hbase.DroppedSn
 import org.apache.hadoop.hbase.HBaseConfiguration;
 import org.apache.hadoop.hbase.HColumnDescriptor;
 import org.apache.hadoop.hbase.HConstants;
+import org.apache.hadoop.hbase.HDFSBlocksDistribution;
 import org.apache.hadoop.hbase.HRegionInfo;
 import org.apache.hadoop.hbase.HTableDescriptor;
 import org.apache.hadoop.hbase.KeyValue;
@@ -461,6 +462,61 @@ public class HRegion implements HeapSize
     return false;
   }
   
+  /**
+   * This function will return the HDFS blocks distribution based on the data
+   * captured when HFile is created
+   * @return The HDFS blocks distribution for the region.
+   */
+  public HDFSBlocksDistribution getHDFSBlocksDistribution() {
+    HDFSBlocksDistribution hdfsBlocksDistribution =
+      new HDFSBlocksDistribution();
+    synchronized (this.stores) {
+      for (Store store : this.stores.values()) {
+        for (StoreFile sf : store.getStorefiles()) {
+          HDFSBlocksDistribution storeFileBlocksDistribution =
+            sf.getHDFSBlockDistribution();
+          hdfsBlocksDistribution.add(storeFileBlocksDistribution);
+        }
+      }
+    }
+    return hdfsBlocksDistribution;
+  }
+
+  /**
+   * This is a helper function to compute HDFS block distribution on demand
+   * @param conf configuration
+   * @param tableDescriptor HTableDescriptor of the table
+   * @param regionEncodedName encoded name of the region
+   * @return The HDFS blocks distribution for the given region.
+ * @throws IOException
+   */
+  static public HDFSBlocksDistribution computeHDFSBlocksDistribution(
+    Configuration conf, HTableDescriptor tableDescriptor,
+    String regionEncodedName) throws IOException {
+    HDFSBlocksDistribution hdfsBlocksDistribution =
+      new HDFSBlocksDistribution();
+    Path tablePath = FSUtils.getTablePath(FSUtils.getRootDir(conf),
+      tableDescriptor.getName());
+    FileSystem fs = tablePath.getFileSystem(conf);
+         
+    for (HColumnDescriptor family: tableDescriptor.getFamilies()) {
+      Path storeHomeDir = Store.getStoreHomedir(tablePath, regionEncodedName,
+      family.getName());
+      if (!fs.exists(storeHomeDir))continue;
+
+      FileStatus[] hfilesStatus = null;
+      hfilesStatus = fs.listStatus(storeHomeDir);
+
+      for (FileStatus hfileStatus : hfilesStatus) {
+        HDFSBlocksDistribution storeFileBlocksDistribution =
+          FSUtils.computeHDFSBlocksDistribution(fs, hfileStatus, 0,
+          hfileStatus.getLen());
+        hdfsBlocksDistribution.add(storeFileBlocksDistribution);
+      }
+    }
+    return hdfsBlocksDistribution;
+  }
+  
   public AtomicLong getMemstoreSize() {
     return memstoreSize;
   }

Modified: 
hbase/trunk/src/main/java/org/apache/hadoop/hbase/regionserver/HRegionServer.java
URL: 
http://svn.apache.org/viewvc/hbase/trunk/src/main/java/org/apache/hadoop/hbase/regionserver/HRegionServer.java?rev=1156390&r1=1156389&r2=1156390&view=diff
==============================================================================
--- 
hbase/trunk/src/main/java/org/apache/hadoop/hbase/regionserver/HRegionServer.java
 (original)
+++ 
hbase/trunk/src/main/java/org/apache/hadoop/hbase/regionserver/HRegionServer.java
 Wed Aug 10 22:16:30 2011
@@ -59,6 +59,7 @@ import org.apache.hadoop.hbase.DoNotRetr
 import org.apache.hadoop.hbase.HBaseConfiguration;
 import org.apache.hadoop.hbase.HConstants;
 import org.apache.hadoop.hbase.HConstants.OperationStatusCode;
+import org.apache.hadoop.hbase.HDFSBlocksDistribution;
 import org.apache.hadoop.hbase.HRegionInfo;
 import org.apache.hadoop.hbase.HServerAddress;
 import org.apache.hadoop.hbase.HServerInfo;
@@ -1210,6 +1211,8 @@ public class HRegionServer implements HR
     int readRequestsCount = 0;
     int writeRequestsCount = 0;
     long storefileIndexSize = 0;
+    HDFSBlocksDistribution hdfsBlocksDistribution =
+      new HDFSBlocksDistribution();
     long totalStaticIndexSize = 0;
     long totalStaticBloomSize = 0;
     for (Map.Entry<String, HRegion> e : this.onlineRegions.entrySet()) {
@@ -1227,6 +1230,8 @@ public class HRegionServer implements HR
             totalStaticBloomSize += store.getTotalStaticBloomSize();
           }
         }
+        
+        hdfsBlocksDistribution.add(r.getHDFSBlocksDistribution());
       }
     this.metrics.stores.set(stores);
     this.metrics.storefiles.set(storefiles);
@@ -1258,6 +1263,11 @@ public class HRegionServer implements HR
       percent = (int) (ratio * 100);
       this.metrics.blockCacheHitCachingRatio.set(percent);
     }
+    float localityIndex = hdfsBlocksDistribution.getBlockLocalityIndex(
+      getServerName().getHostname());
+    int percent = (int) (localityIndex * 100);
+    this.metrics.hdfsBlocksLocalityIndex.set(percent);
+    
   }
 
   /**

Modified: 
hbase/trunk/src/main/java/org/apache/hadoop/hbase/regionserver/StoreFile.java
URL: 
http://svn.apache.org/viewvc/hbase/trunk/src/main/java/org/apache/hadoop/hbase/regionserver/StoreFile.java?rev=1156390&r1=1156389&r2=1156390&view=diff
==============================================================================
--- 
hbase/trunk/src/main/java/org/apache/hadoop/hbase/regionserver/StoreFile.java 
(original)
+++ 
hbase/trunk/src/main/java/org/apache/hadoop/hbase/regionserver/StoreFile.java 
Wed Aug 10 22:16:30 2011
@@ -29,7 +29,6 @@ import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
-import java.util.List;
 import java.util.Map;
 import java.util.Random;
 import java.util.SortedSet;
@@ -43,6 +42,7 @@ import org.apache.hadoop.conf.Configurat
 import org.apache.hadoop.fs.FileStatus;
 import org.apache.hadoop.fs.FileSystem;
 import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.hbase.HDFSBlocksDistribution;
 import org.apache.hadoop.hbase.KeyValue;
 import org.apache.hadoop.hbase.KeyValue.KVComparator;
 import org.apache.hadoop.hbase.client.Scan;
@@ -58,6 +58,7 @@ import org.apache.hadoop.hbase.util.Bloo
 import org.apache.hadoop.hbase.util.BloomFilterFactory;
 import org.apache.hadoop.hbase.util.BloomFilterWriter;
 import org.apache.hadoop.hbase.util.Bytes;
+import org.apache.hadoop.hbase.util.FSUtils;
 import org.apache.hadoop.hbase.util.Writables;
 import org.apache.hadoop.io.RawComparator;
 import org.apache.hadoop.io.WritableUtils;
@@ -143,6 +144,9 @@ public class StoreFile {
   // Is this from an in-memory store
   private boolean inMemory;
 
+  // HDFS blocks distribuion information
+  private HDFSBlocksDistribution hdfsBlocksDistribution;
+
   // Keys for metadata stored in backing HFile.
   // Set when we obtain a Reader.
   private long sequenceid = -1;
@@ -384,6 +388,84 @@ public class StoreFile {
     return blockcache ? getBlockCache(conf) : null;
   }
 
+
+  /**
+   * @return the cached value of HDFS blocks distribution. The cached value is
+   * calculated when store file is opened.
+   */  
+  public HDFSBlocksDistribution getHDFSBlockDistribution() {
+    return this.hdfsBlocksDistribution;
+  }
+
+  /**
+   * helper function to compute HDFS blocks distribution of a given reference
+   * file.For reference file, we don't compute the exact value. We use some
+   * estimate instead given it might be good enough. we assume bottom part
+   * takes the first half of reference file, top part takes the second half
+   * of the reference file. This is just estimate, given
+   * midkey ofregion != midkey of HFile, also the number and size of keys vary.
+   * If this estimate isn't good enough, we can improve it later.
+   * @param fs  The FileSystem
+   * @param reference  The reference
+   * @param reference  The referencePath
+   * @return HDFS blocks distribution
+   */    
+  static private HDFSBlocksDistribution computeRefFileHDFSBlockDistribution(
+    FileSystem fs, Reference reference, Path referencePath) throws IOException 
{
+    if ( referencePath == null) {
+      return null;
+    }
+    
+    FileStatus status = fs.getFileStatus(referencePath);
+    long start = 0;
+    long length = 0;
+    
+    if (Reference.isTopFileRegion(reference.getFileRegion())) {
+      start = status.getLen()/2;
+      length = status.getLen() - status.getLen()/2;
+    } else {
+      start = 0;
+      length = status.getLen()/2;
+    }
+    return FSUtils.computeHDFSBlocksDistribution(fs, status, start, length);
+  }
+  
+  /**
+   * helper function to compute HDFS blocks distribution of a given file.
+   * For reference file, it is an estimate
+   * @param fs  The FileSystem
+   * @param o  The path of the file
+   * @return HDFS blocks distribution
+   */    
+  static public HDFSBlocksDistribution computeHDFSBlockDistribution(
+    FileSystem fs, Path p) throws IOException {
+    if (isReference(p)) {
+      Reference reference = Reference.read(fs, p);
+      Path referencePath = getReferredToFile(p);
+      return computeRefFileHDFSBlockDistribution(fs, reference, referencePath);
+    } else {
+      FileStatus status = fs.getFileStatus(p);
+      long length = status.getLen();
+      return FSUtils.computeHDFSBlocksDistribution(fs, status, 0, length);
+    }
+  }
+  
+  
+  /**
+   * compute HDFS block distribution, for reference file, it is an estimate
+   */
+  private void computeHDFSBlockDistribution() throws IOException {
+    if (isReference()) {
+      this.hdfsBlocksDistribution = computeRefFileHDFSBlockDistribution(
+        this.fs, this.reference, this.referencePath);
+    } else {
+      FileStatus status = this.fs.getFileStatus(this.path);
+      long length = status.getLen();
+      this.hdfsBlocksDistribution = FSUtils.computeHDFSBlocksDistribution(
+        this.fs, status, 0, length);
+    }
+  }
+  
   /**
    * Opens reader on this store file.  Called by Constructor.
    * @return Reader for the store file.
@@ -402,6 +484,9 @@ public class StoreFile {
           this.inMemory,
           this.conf.getBoolean(HFile.EVICT_BLOCKS_ON_CLOSE_KEY, true));
     }
+    
+    computeHDFSBlockDistribution();
+    
     // Load up indices and fileinfo.
     metadataMap = Collections.unmodifiableMap(this.reader.loadFileInfo());
     // Read in our metadata.
@@ -1235,5 +1320,4 @@ public class StoreFile {
         }
       });
   }
-
 }

Modified: 
hbase/trunk/src/main/java/org/apache/hadoop/hbase/regionserver/metrics/RegionServerMetrics.java
URL: 
http://svn.apache.org/viewvc/hbase/trunk/src/main/java/org/apache/hadoop/hbase/regionserver/metrics/RegionServerMetrics.java?rev=1156390&r1=1156389&r2=1156390&view=diff
==============================================================================
--- 
hbase/trunk/src/main/java/org/apache/hadoop/hbase/regionserver/metrics/RegionServerMetrics.java
 (original)
+++ 
hbase/trunk/src/main/java/org/apache/hadoop/hbase/regionserver/metrics/RegionServerMetrics.java
 Wed Aug 10 22:16:30 2011
@@ -156,6 +156,12 @@ public class RegionServerMetrics impleme
     new MetricsIntValue("totalStaticBloomSizeKB", registry);
 
   /**
+   * HDFS blocks locality index
+   */
+  public final MetricsIntValue hdfsBlocksLocalityIndex =
+    new MetricsIntValue("hdfsBlocksLocalityIndex", registry);
+  
+  /**
    * Sum of all the memstore sizes in this regionserver in MB
    */
   public final MetricsIntValue memstoreSizeMB =
@@ -282,6 +288,7 @@ public class RegionServerMetrics impleme
       this.blockCacheEvictedCount.pushMetric(this.metricsRecord);
       this.blockCacheHitRatio.pushMetric(this.metricsRecord);
       this.blockCacheHitCachingRatio.pushMetric(this.metricsRecord);
+      this.hdfsBlocksLocalityIndex.pushMetric(this.metricsRecord);
 
       // Mix in HFile and HLog metrics
       // Be careful. Here is code for MTVR from up in hadoop:
@@ -407,6 +414,8 @@ public class RegionServerMetrics impleme
         Long.valueOf(this.blockCacheHitRatio.get())+"%");
     sb = Strings.appendKeyValue(sb, this.blockCacheHitCachingRatio.getName(),
         Long.valueOf(this.blockCacheHitCachingRatio.get())+"%");
+    sb = Strings.appendKeyValue(sb, this.hdfsBlocksLocalityIndex.getName(),
+        Long.valueOf(this.hdfsBlocksLocalityIndex.get()));
     return sb.toString();
   }
 }

Modified: hbase/trunk/src/main/java/org/apache/hadoop/hbase/util/FSUtils.java
URL: 
http://svn.apache.org/viewvc/hbase/trunk/src/main/java/org/apache/hadoop/hbase/util/FSUtils.java?rev=1156390&r1=1156389&r2=1156390&view=diff
==============================================================================
--- hbase/trunk/src/main/java/org/apache/hadoop/hbase/util/FSUtils.java 
(original)
+++ hbase/trunk/src/main/java/org/apache/hadoop/hbase/util/FSUtils.java Wed Aug 
10 22:16:30 2011
@@ -22,6 +22,7 @@ package org.apache.hadoop.hbase.util;
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.BlockLocation;
 import org.apache.hadoop.fs.FSDataInputStream;
 import org.apache.hadoop.fs.FSDataOutputStream;
 import org.apache.hadoop.fs.FileStatus;
@@ -29,6 +30,7 @@ import org.apache.hadoop.fs.FileSystem;
 import org.apache.hadoop.fs.Path;
 import org.apache.hadoop.fs.PathFilter;
 import org.apache.hadoop.hbase.HConstants;
+import org.apache.hadoop.hbase.HDFSBlocksDistribution;
 import org.apache.hadoop.hbase.HRegionInfo;
 import org.apache.hadoop.hbase.HTableDescriptor;
 import org.apache.hadoop.hbase.RemoteExceptionHandler;
@@ -488,6 +490,32 @@ public abstract class FSUtils {
     return fs.exists(rootRegionDir);
   }
 
+
+  /**
+   * Compute HDFS blocks distribution of a given file, or a portion of the file
+   * @param fs file system
+   * @param FileStatus file status of the file
+   * @param start start position of the portion
+   * @param length length of the portion 
+   * @return The HDFS blocks distribution
+   */  
+  static public HDFSBlocksDistribution computeHDFSBlocksDistribution(
+    final FileSystem fs, FileStatus status, long start, long length)
+    throws IOException {
+    HDFSBlocksDistribution blocksDistribution = new HDFSBlocksDistribution();
+    BlockLocation [] blockLocations =
+      fs.getFileBlockLocations(status, start, length);
+    for(BlockLocation bl : blockLocations) {
+      String [] hosts = bl.getHosts();
+      long len = bl.getLength();
+      blocksDistribution.addHostsAndBlockWeight(hosts, len);
+    }
+    
+    return blocksDistribution;
+  }
+  
+
+  
   /**
    * Runs through the hbase rootdir and checks all stores have only
    * one file in them -- that is, they've been major compacted.  Looks

Modified: 
hbase/trunk/src/test/java/org/apache/hadoop/hbase/HBaseTestingUtility.java
URL: 
http://svn.apache.org/viewvc/hbase/trunk/src/test/java/org/apache/hadoop/hbase/HBaseTestingUtility.java?rev=1156390&r1=1156389&r2=1156390&view=diff
==============================================================================
--- hbase/trunk/src/test/java/org/apache/hadoop/hbase/HBaseTestingUtility.java 
(original)
+++ hbase/trunk/src/test/java/org/apache/hadoop/hbase/HBaseTestingUtility.java 
Wed Aug 10 22:16:30 2011
@@ -223,7 +223,27 @@ public class HBaseTestingUtility {
    * @return The mini dfs cluster created.
    */
   public MiniDFSCluster startMiniDFSCluster(int servers) throws Exception {
-    return startMiniDFSCluster(servers, null);
+    return startMiniDFSCluster(servers, null, null);
+  }
+
+  /**
+   * Start a minidfscluster.
+   * This is useful if you want to run datanode on distinct hosts for things
+   * like HDFS block location verification.
+   * If you start MiniDFSCluster without host names, all instances of the
+   * datanodes will have the same host name.
+   * @param hosts hostnames DNs to run on.
+   * @throws Exception
+   * @see {@link #shutdownMiniDFSCluster()}
+   * @return The mini dfs cluster created.
+   */
+  public MiniDFSCluster startMiniDFSCluster(final String hosts[])
+    throws Exception {
+    if ( hosts != null && hosts.length != 0) {
+      return startMiniDFSCluster(hosts.length, null, hosts);
+    } else {
+      return startMiniDFSCluster(1, null, null);
+    }
   }
 
   /**
@@ -237,6 +257,22 @@ public class HBaseTestingUtility {
    */
   public MiniDFSCluster startMiniDFSCluster(int servers, final File dir)
   throws Exception {
+    return startMiniDFSCluster(servers, dir, null);
+  }
+
+  
+  /**
+   * Start a minidfscluster.
+   * Can only create one.
+   * @param servers How many DNs to start.
+   * @param dir Where to home your dfs cluster.
+   * @param hosts hostnames DNs to run on.
+   * @throws Exception
+   * @see {@link #shutdownMiniDFSCluster()}
+   * @return The mini dfs cluster created.
+   */
+  public MiniDFSCluster startMiniDFSCluster(int servers, final File dir, final 
String hosts[])
+  throws Exception {
     // This does the following to home the minidfscluster
     //     base_dir = new File(System.getProperty("test.build.data", 
"build/test/data"), "dfs/");
     // Some tests also do this:
@@ -249,7 +285,7 @@ public class HBaseTestingUtility {
     System.setProperty(TEST_DIRECTORY_KEY, 
this.clusterTestBuildDir.toString());
     System.setProperty("test.cache.data", this.clusterTestBuildDir.toString());
     this.dfsCluster = new MiniDFSCluster(0, this.conf, servers, true, true,
-      true, null, null, null, null);
+      true, null, null, hosts, null);
     // Set this just-started cluser as our filesystem.
     FileSystem fs = this.dfsCluster.getFileSystem();
     this.conf.set("fs.defaultFS", fs.getUri().toString());
@@ -356,6 +392,20 @@ public class HBaseTestingUtility {
     return startMiniCluster(1, numSlaves);
   }
 
+  
+  /**
+   * start minicluster
+   * @throws Exception
+   * @see {@link #shutdownMiniCluster()}
+   * @return Mini hbase cluster instance created.
+   */
+  public MiniHBaseCluster startMiniCluster(final int numMasters,
+    final int numSlaves)
+  throws Exception {
+    return startMiniCluster(numMasters, numSlaves, null);
+  }
+  
+  
   /**
    * Start up a minicluster of hbase, optionally dfs, and zookeeper.
    * Modifies Configuration.  Homes the cluster data directory under a random
@@ -365,18 +415,31 @@ public class HBaseTestingUtility {
    * hbase masters.  If numMasters > 1, you can find the active/primary master
    * with {@link MiniHBaseCluster#getMaster()}.
    * @param numSlaves Number of slaves to start up.  We'll start this many
-   * datanodes and regionservers.  If numSlaves is > 1, then make sure
+   * regionservers. If dataNodeHosts == null, this also indicates the number of
+   * datanodes to start. If dataNodeHosts != null, the number of datanodes is
+   * based on dataNodeHosts.length.
+   * If numSlaves is > 1, then make sure
    * hbase.regionserver.info.port is -1 (i.e. no ui per regionserver) otherwise
    * bind errors.
+   * @param dataNodeHosts hostnames DNs to run on.
+   * This is useful if you want to run datanode on distinct hosts for things
+   * like HDFS block location verification.
+   * If you start MiniDFSCluster without host names,
+   * all instances of the datanodes will have the same host name.
    * @throws Exception
    * @see {@link #shutdownMiniCluster()}
    * @return Mini hbase cluster instance created.
    */
   public MiniHBaseCluster startMiniCluster(final int numMasters,
-      final int numSlaves)
+    final int numSlaves, final String[] dataNodeHosts)
   throws Exception {
+    int numDataNodes = numSlaves;
+    if ( dataNodeHosts != null && dataNodeHosts.length != 0) {
+      numDataNodes = dataNodeHosts.length;
+    }
+    
     LOG.info("Starting up minicluster with " + numMasters + " master(s) and " +
-        numSlaves + " regionserver(s) and datanode(s)");
+        numSlaves + " regionserver(s) and " + numDataNodes + " datanode(s)");
     // If we already put up a cluster, fail.
     String testBuildPath = conf.get(TEST_DIRECTORY_KEY, null);
     isRunningCluster(testBuildPath);
@@ -390,7 +453,7 @@ public class HBaseTestingUtility {
     System.setProperty(TEST_DIRECTORY_KEY, this.clusterTestBuildDir.getPath());
     // Bring up mini dfs cluster. This spews a bunch of warnings about missing
     // scheme. Complaints are 'Scheme is undefined for 
build/test/data/dfs/name1'.
-    startMiniDFSCluster(numSlaves, this.clusterTestBuildDir);
+    startMiniDFSCluster(numDataNodes, this.clusterTestBuildDir, dataNodeHosts);
     this.dfsCluster.waitClusterUp();
 
     // Start up a zk cluster.

Modified: 
hbase/trunk/src/test/java/org/apache/hadoop/hbase/regionserver/TestHRegion.java
URL: 
http://svn.apache.org/viewvc/hbase/trunk/src/test/java/org/apache/hadoop/hbase/regionserver/TestHRegion.java?rev=1156390&r1=1156389&r2=1156390&view=diff
==============================================================================
--- 
hbase/trunk/src/test/java/org/apache/hadoop/hbase/regionserver/TestHRegion.java 
(original)
+++ 
hbase/trunk/src/test/java/org/apache/hadoop/hbase/regionserver/TestHRegion.java 
Wed Aug 10 22:16:30 2011
@@ -19,6 +19,7 @@
  */
 package org.apache.hadoop.hbase.regionserver;
 
+
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -33,6 +34,8 @@ import java.util.concurrent.atomic.Atomi
 import org.apache.commons.logging.Log;
 import org.apache.commons.logging.LogFactory;
 import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.hdfs.MiniDFSCluster;
+import org.apache.hadoop.fs.FileSystem;
 import org.apache.hadoop.fs.Path;
 import org.apache.hadoop.hbase.DoNotRetryIOException;
 import org.apache.hadoop.hbase.HBaseConfiguration;
@@ -40,17 +43,21 @@ import org.apache.hadoop.hbase.HBaseTest
 import org.apache.hadoop.hbase.HBaseTestingUtility;
 import org.apache.hadoop.hbase.HColumnDescriptor;
 import org.apache.hadoop.hbase.HConstants;
+import org.apache.hadoop.hbase.HDFSBlocksDistribution;
+import org.apache.hadoop.hbase.ServerName;
 import org.apache.hadoop.hbase.HConstants.OperationStatusCode;
 import org.apache.hadoop.hbase.HRegionInfo;
 import org.apache.hadoop.hbase.HTableDescriptor;
 import org.apache.hadoop.hbase.KeyValue;
 import org.apache.hadoop.hbase.MultithreadedTestUtil;
 import org.apache.hadoop.hbase.MultithreadedTestUtil.TestThread;
+import org.apache.hadoop.hbase.MiniHBaseCluster;
 import org.apache.hadoop.hbase.client.Delete;
 import org.apache.hadoop.hbase.client.Get;
 import org.apache.hadoop.hbase.client.Put;
 import org.apache.hadoop.hbase.client.Result;
 import org.apache.hadoop.hbase.client.Scan;
+import org.apache.hadoop.hbase.client.HTable;
 import org.apache.hadoop.hbase.filter.BinaryComparator;
 import org.apache.hadoop.hbase.filter.ColumnCountGetFilter;
 import org.apache.hadoop.hbase.filter.CompareFilter.CompareOp;
@@ -69,6 +76,7 @@ import org.apache.hadoop.hbase.util.Manu
 import org.apache.hadoop.hbase.util.Pair;
 import org.apache.hadoop.hbase.util.PairOfSameType;
 import org.apache.hadoop.hbase.util.Threads;
+import org.junit.Test;
 
 import com.google.common.collect.Lists;
 
@@ -2962,6 +2970,64 @@ public class TestHRegion extends HBaseTe
     assertTrue(keyValues.length == 0);
   }
 
+  @Test public void testgetHDFSBlocksDistribution() throws Exception {
+    HBaseTestingUtility htu = new HBaseTestingUtility();
+    final int DEFAULT_BLOCK_SIZE = 1024;
+    htu.getConfiguration().setLong("dfs.block.size", DEFAULT_BLOCK_SIZE);
+    htu.getConfiguration().setInt("dfs.replication", 2);
+    
+    
+    // set up a cluster with 3 nodes
+    MiniHBaseCluster cluster;
+    String dataNodeHosts[] = new String[] { "host1", "host2", "host3" };
+    int regionServersCount = 3;
+           
+    try {
+      cluster = htu.startMiniCluster(1, regionServersCount, dataNodeHosts);
+      byte [][] families = {fam1, fam2};
+      HTable ht = htu.createTable(Bytes.toBytes(this.getName()), families);
+
+      //Setting up region
+      byte row[] = Bytes.toBytes("row1");
+      byte col[] = Bytes.toBytes("col1");
+
+      Put put = new Put(row);          
+      put.add(fam1, col, 1, Bytes.toBytes("test1"));
+      put.add(fam2, col, 1, Bytes.toBytes("test2"));
+      ht.put(put);
+      
+      HRegion firstRegion = htu.getHbaseCluster().
+        getRegions(Bytes.toBytes(this.getName())).get(0);
+      firstRegion.flushcache();
+      HDFSBlocksDistribution blocksDistribution1 =
+        firstRegion.getHDFSBlocksDistribution();
+      
+      // given the default replication factor is 2 and we have 2 HFiles,
+      // we will have total of 4 replica of blocks on 3 datanodes; thus there
+      // must be at least one host that have replica for 2 HFiles. That host's
+      // weight will be equal to the unique block weight.
+      long uniqueBlocksWeight1 =
+        blocksDistribution1.getUniqueBlocksTotalWeight();
+      
+      String topHost = blocksDistribution1.getTopHosts().get(0);
+      long topHostWeight = blocksDistribution1.getWeight(topHost);
+      assertTrue(uniqueBlocksWeight1 == topHostWeight);
+      
+      // use the static method to compute the value, it should be the same.
+      // static method is used by load balancer or other components
+      HDFSBlocksDistribution blocksDistribution2 = 
+        HRegion.computeHDFSBlocksDistribution(htu.getConfiguration(),
+        firstRegion.getTableDesc(),
+        firstRegion.getRegionInfo().getEncodedName());
+      long uniqueBlocksWeight2 =
+        blocksDistribution2.getUniqueBlocksTotalWeight();
+
+      assertTrue(uniqueBlocksWeight1 == uniqueBlocksWeight2);
+      } finally {
+        htu.shutdownMiniCluster();
+      }
+  }
+  
   private void putData(int startRow, int numRows, byte [] qf,
       byte [] ...families)
   throws IOException {

Modified: 
hbase/trunk/src/test/java/org/apache/hadoop/hbase/util/TestFSUtils.java
URL: 
http://svn.apache.org/viewvc/hbase/trunk/src/test/java/org/apache/hadoop/hbase/util/TestFSUtils.java?rev=1156390&r1=1156389&r2=1156390&view=diff
==============================================================================
--- hbase/trunk/src/test/java/org/apache/hadoop/hbase/util/TestFSUtils.java 
(original)
+++ hbase/trunk/src/test/java/org/apache/hadoop/hbase/util/TestFSUtils.java Wed 
Aug 10 22:16:30 2011
@@ -23,6 +23,11 @@ import static org.junit.Assert.assertFal
 import static org.junit.Assert.assertTrue;
 
 import org.apache.hadoop.hbase.HBaseTestingUtility;
+import org.apache.hadoop.hbase.HDFSBlocksDistribution;
+import org.apache.hadoop.fs.FSDataOutputStream;
+import org.apache.hadoop.fs.FileSystem;
+import org.apache.hadoop.fs.FileStatus;
+import org.apache.hadoop.fs.Path;
 import org.apache.hadoop.hdfs.MiniDFSCluster;
 import org.junit.Test;
 
@@ -30,6 +35,7 @@ import org.junit.Test;
  * Test {@link FSUtils}.
  */
 public class TestFSUtils {
+  
   @Test public void testIsHDFS() throws Exception {
     HBaseTestingUtility htu = new HBaseTestingUtility();
     htu.getConfiguration().setBoolean("dfs.support.append", false);
@@ -44,4 +50,100 @@ public class TestFSUtils {
       if (cluster != null) cluster.shutdown();
     }
   }
+  
+  private void WriteDataToHDFS(FileSystem fs, Path file, int dataSize)
+    throws Exception {
+    FSDataOutputStream out = fs.create(file);
+    byte [] data = new byte[dataSize];
+    out.write(data, 0, dataSize);
+    out.close();
+  }
+  
+  @Test public void testcomputeHDFSBlocksDistribution() throws Exception {
+    HBaseTestingUtility htu = new HBaseTestingUtility();
+    final int DEFAULT_BLOCK_SIZE = 1024;
+    htu.getConfiguration().setLong("dfs.block.size", DEFAULT_BLOCK_SIZE);
+    MiniDFSCluster cluster = null;
+    Path testFile = null;
+    
+    try {
+      // set up a cluster with 3 nodes
+      String hosts[] = new String[] { "host1", "host2", "host3" };
+      cluster = htu.startMiniDFSCluster(hosts);
+      cluster.waitActive();
+      FileSystem fs = cluster.getFileSystem();
+
+      // create a file with two blocks
+      testFile = new Path("/test1.txt");
+      WriteDataToHDFS(fs, testFile, 2*DEFAULT_BLOCK_SIZE);
+      
+      // given the default replication factor is 3, the same as the number of
+      // datanodes; the locality index for each host should be 100%,
+      // or getWeight for each host should be the same as 
getUniqueBlocksWeights
+      FileStatus status = fs.getFileStatus(testFile);
+      HDFSBlocksDistribution blocksDistribution =
+        FSUtils.computeHDFSBlocksDistribution(fs, status, 0, status.getLen());
+      long uniqueBlocksTotalWeight =
+        blocksDistribution.getUniqueBlocksTotalWeight();
+      for (String host : hosts) {
+        long weight = blocksDistribution.getWeight(host);
+        assertTrue(uniqueBlocksTotalWeight == weight);
+      }
+    } finally {
+      htu.shutdownMiniDFSCluster();
+    }
+
+    
+    try {
+      // set up a cluster with 4 nodes
+      String hosts[] = new String[] { "host1", "host2", "host3", "host4" };
+      cluster = htu.startMiniDFSCluster(hosts);
+      cluster.waitActive();
+      FileSystem fs = cluster.getFileSystem();
+
+      // create a file with three blocks
+      testFile = new Path("/test2.txt");        
+      WriteDataToHDFS(fs, testFile, 3*DEFAULT_BLOCK_SIZE);
+              
+      // given the default replication factor is 3, we will have total of 9
+      // replica of blocks; thus the host with the highest weight should have
+      // weight == 3 * DEFAULT_BLOCK_SIZE
+      FileStatus status = fs.getFileStatus(testFile);
+      HDFSBlocksDistribution blocksDistribution =
+        FSUtils.computeHDFSBlocksDistribution(fs, status, 0, status.getLen());
+      long uniqueBlocksTotalWeight =
+        blocksDistribution.getUniqueBlocksTotalWeight();
+      
+      String tophost = blocksDistribution.getTopHosts().get(0);
+      long weight = blocksDistribution.getWeight(tophost);
+      assertTrue(uniqueBlocksTotalWeight == weight);
+      
+    } finally {
+      htu.shutdownMiniDFSCluster();
+    }
+
+    
+    try {
+      // set up a cluster with 4 nodes
+      String hosts[] = new String[] { "host1", "host2", "host3", "host4" };
+      cluster = htu.startMiniDFSCluster(hosts);
+      cluster.waitActive();
+      FileSystem fs = cluster.getFileSystem();
+
+      // create a file with one block
+      testFile = new Path("/test3.txt");        
+      WriteDataToHDFS(fs, testFile, DEFAULT_BLOCK_SIZE);
+      
+      // given the default replication factor is 3, we will have total of 3
+      // replica of blocks; thus there is one host without weight
+      FileStatus status = fs.getFileStatus(testFile);
+      HDFSBlocksDistribution blocksDistribution =
+        FSUtils.computeHDFSBlocksDistribution(fs, status, 0, status.getLen());
+      assertTrue(blocksDistribution.getTopHosts().size() == 3);
+    } finally {
+      htu.shutdownMiniDFSCluster();
+    }
+    
+  }
+  
 }
\ No newline at end of file


Reply via email to