001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019package org.apache.hadoop.hdfs.server.namenode;
020
021import java.text.NumberFormat;
022import java.util.Arrays;
023import java.util.Collections;
024import java.util.Comparator;
025import java.util.EnumMap;
026import java.util.Formatter;
027import java.util.HashMap;
028import java.util.LinkedList;
029import java.util.List;
030import java.util.Map;
031import java.util.Map.Entry;
032import java.util.Objects;
033
034import org.apache.hadoop.fs.StorageType;
035import org.apache.hadoop.hdfs.protocol.BlockStoragePolicy;
036
037/**
038 * Aggregate the storage type information for a set of blocks
039 *
040 */
041public class StoragePolicySummary {
042
043  Map<StorageTypeAllocation, Long> storageComboCounts = new HashMap<>();
044  final BlockStoragePolicy[] storagePolicies;
045  int totalBlocks;
046
047  StoragePolicySummary(BlockStoragePolicy[] storagePolicies) {
048    this.storagePolicies = storagePolicies;
049  }
050
051  // Add a storage type combination
052  void add(StorageType[] storageTypes, BlockStoragePolicy policy) {
053    StorageTypeAllocation storageCombo = 
054        new StorageTypeAllocation(storageTypes, policy);
055    Long count = storageComboCounts.get(storageCombo);
056    if (count == null) {
057      storageComboCounts.put(storageCombo, 1l);
058      storageCombo.setActualStoragePolicy(
059          getStoragePolicy(storageCombo.getStorageTypes()));
060    } else {
061      storageComboCounts.put(storageCombo, count.longValue()+1);
062    }
063    totalBlocks++;
064  }
065
066  // sort the storageType combinations based on the total blocks counts
067  // in descending order
068  static List<Entry<StorageTypeAllocation, Long>> sortByComparator(
069      Map<StorageTypeAllocation, Long> unsortMap) {
070    List<Entry<StorageTypeAllocation, Long>> storageAllocations = 
071        new LinkedList<>(unsortMap.entrySet());
072    // Sorting the list based on values
073    Collections.sort(storageAllocations, 
074      new Comparator<Entry<StorageTypeAllocation, Long>>() {
075          public int compare(Entry<StorageTypeAllocation, Long> o1,
076              Entry<StorageTypeAllocation, Long> o2)
077          {
078            return o2.getValue().compareTo(o1.getValue());
079          }
080    });
081    return storageAllocations;
082  }
083
084  public String toString() {
085    StringBuilder compliantBlocksSB = new StringBuilder();
086    compliantBlocksSB.append("\nBlocks satisfying the specified storage policy:");
087    compliantBlocksSB.append("\nStorage Policy                  # of blocks       % of blocks\n");
088    StringBuilder nonCompliantBlocksSB = new StringBuilder();
089    Formatter compliantFormatter = new Formatter(compliantBlocksSB);
090    Formatter nonCompliantFormatter = new Formatter(nonCompliantBlocksSB);
091    NumberFormat percentFormat = NumberFormat.getPercentInstance();
092    percentFormat.setMinimumFractionDigits(4);
093    percentFormat.setMaximumFractionDigits(4);
094    for (Map.Entry<StorageTypeAllocation, Long> storageComboCount:
095      sortByComparator(storageComboCounts)) {
096      double percent = (double) storageComboCount.getValue() / 
097          (double) totalBlocks;
098      StorageTypeAllocation sta = storageComboCount.getKey();
099      if (sta.policyMatches()) {
100        compliantFormatter.format("%-25s %10d  %20s%n",
101            sta.getStoragePolicyDescriptor(),
102            storageComboCount.getValue(),
103            percentFormat.format(percent));
104      } else {
105        if (nonCompliantBlocksSB.length() == 0) {
106          nonCompliantBlocksSB.append("\nBlocks NOT satisfying the specified storage policy:");
107          nonCompliantBlocksSB.append("\nStorage Policy                  ");
108          nonCompliantBlocksSB.append(
109              "Specified Storage Policy      # of blocks       % of blocks\n");
110        }
111        nonCompliantFormatter.format("%-35s %-20s %10d  %20s%n",
112            sta.getStoragePolicyDescriptor(),
113            sta.getSpecifiedStoragePolicy().getName(),
114            storageComboCount.getValue(),
115            percentFormat.format(percent));
116      }
117    }
118    if (nonCompliantBlocksSB.length() == 0) {
119      nonCompliantBlocksSB.append("\nAll blocks satisfy specified storage policy.\n");
120    }
121    compliantFormatter.close();
122    nonCompliantFormatter.close();
123    return compliantBlocksSB.toString() + nonCompliantBlocksSB;
124  }
125
126  /**
127   * 
128   * @param storageTypes - sorted array of storageTypes
129   * @return Storage Policy which matches the specific storage Combination
130   */
131  private BlockStoragePolicy getStoragePolicy(StorageType[] storageTypes) {
132    for (BlockStoragePolicy storagePolicy:storagePolicies) {
133      StorageType[] policyStorageTypes = storagePolicy.getStorageTypes();
134      policyStorageTypes = Arrays.copyOf(policyStorageTypes, policyStorageTypes.length);
135      Arrays.sort(policyStorageTypes);
136      if (policyStorageTypes.length <= storageTypes.length) {
137        int i = 0; 
138        for (; i < policyStorageTypes.length; i++) {
139          if (policyStorageTypes[i] != storageTypes[i]) {
140            break;
141          }
142        }
143        if (i < policyStorageTypes.length) {
144          continue;
145        }
146        int j=policyStorageTypes.length;
147        for (; j < storageTypes.length; j++) {
148          if (policyStorageTypes[i-1] != storageTypes[j]) {
149            break;
150          }
151        }
152
153        if (j==storageTypes.length) {
154          return storagePolicy;
155        }
156      }
157    }
158    return null;
159  }
160
161  /**
162   * Internal class which represents a unique Storage type combination
163   *
164   */
165  static class StorageTypeAllocation {
166    private final BlockStoragePolicy specifiedStoragePolicy;
167    private final StorageType[] storageTypes;
168    private BlockStoragePolicy actualStoragePolicy;
169
170    StorageTypeAllocation(StorageType[] storageTypes, 
171        BlockStoragePolicy specifiedStoragePolicy) {
172      Arrays.sort(storageTypes);
173      this.storageTypes = storageTypes;
174      this.specifiedStoragePolicy = specifiedStoragePolicy;
175    }
176    
177    StorageType[] getStorageTypes() {
178      return storageTypes;
179    }
180
181    BlockStoragePolicy getSpecifiedStoragePolicy() {
182      return specifiedStoragePolicy;
183    }
184    
185    void setActualStoragePolicy(BlockStoragePolicy actualStoragePolicy) {
186      this.actualStoragePolicy = actualStoragePolicy;
187    }
188    
189    BlockStoragePolicy getActualStoragePolicy() {
190      return actualStoragePolicy;
191    }
192
193    private static String getStorageAllocationAsString
194      (Map<StorageType, Integer> storageType_countmap) {
195      StringBuilder sb = new StringBuilder();
196      for (Map.Entry<StorageType, Integer> 
197      storageTypeCountEntry:storageType_countmap.entrySet()) {
198        sb.append(storageTypeCountEntry.getKey().name()+ ":"
199            + storageTypeCountEntry.getValue() + ",");
200      }
201      if (sb.length() > 1) {
202        sb.deleteCharAt(sb.length()-1);
203      }
204      return sb.toString();
205    }
206
207    private String getStorageAllocationAsString() {
208      Map<StorageType, Integer> storageType_countmap = 
209          new EnumMap<>(StorageType.class);
210      for (StorageType storageType: storageTypes) {
211        Integer count = storageType_countmap.get(storageType);
212        if (count == null) {
213          storageType_countmap.put(storageType, 1);
214        } else {
215          storageType_countmap.put(storageType, count.intValue()+1);
216        }
217      }
218      return (getStorageAllocationAsString(storageType_countmap));
219    }
220    
221    String getStoragePolicyDescriptor() {
222      StringBuilder storagePolicyDescriptorSB = new StringBuilder();
223      if (actualStoragePolicy!=null) {
224        storagePolicyDescriptorSB.append(getStorageAllocationAsString())
225        .append("(")
226        .append(actualStoragePolicy.getName())
227        .append(")");
228      } else {
229        storagePolicyDescriptorSB.append(getStorageAllocationAsString());
230      }
231      return storagePolicyDescriptorSB.toString();
232    }
233    
234    boolean policyMatches() {
235      return specifiedStoragePolicy.equals(actualStoragePolicy);
236    }
237    
238    @Override
239    public String toString() {
240      return specifiedStoragePolicy.getName() + "|" + getStoragePolicyDescriptor();
241    }
242
243    @Override
244    public int hashCode() {
245      return Objects.hash(specifiedStoragePolicy,Arrays.hashCode(storageTypes));
246    }
247
248    @Override
249    public boolean equals(Object another) {
250      return (another instanceof StorageTypeAllocation && 
251          Objects.equals(specifiedStoragePolicy,
252              ((StorageTypeAllocation)another).specifiedStoragePolicy) &&
253              Arrays.equals(storageTypes,
254                  ((StorageTypeAllocation)another).storageTypes));
255    }
256  }
257}