1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.hadoop.hbase.io.hfile;
19
20 import java.io.IOException;
21 import java.nio.ByteBuffer;
22 import java.util.NavigableMap;
23 import java.util.NavigableSet;
24 import java.util.concurrent.ConcurrentSkipListMap;
25 import java.util.concurrent.ConcurrentSkipListSet;
26
27 import org.apache.commons.logging.Log;
28 import org.apache.commons.logging.LogFactory;
29 import org.apache.hadoop.hbase.classification.InterfaceAudience;
30 import org.apache.hadoop.conf.Configuration;
31 import org.apache.hadoop.hbase.metrics.impl.FastLongHistogram;
32 import org.apache.hadoop.hbase.util.Bytes;
33 import org.codehaus.jackson.JsonGenerationException;
34 import org.codehaus.jackson.annotate.JsonIgnoreProperties;
35 import org.codehaus.jackson.map.JsonMappingException;
36 import org.codehaus.jackson.map.ObjectMapper;
37 import org.codehaus.jackson.map.SerializationConfig;
38
39
40
41
42
43 @InterfaceAudience.Private
44 public class BlockCacheUtil {
45
46 private static final Log LOG = LogFactory.getLog(BlockCacheUtil.class);
47
48 public static final long NANOS_PER_SECOND = 1000000000;
49
50
51
52
53 private static final ObjectMapper MAPPER = new ObjectMapper();
54 static {
55 MAPPER.configure(SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS, false);
56 MAPPER.configure(SerializationConfig.Feature.FLUSH_AFTER_WRITE_VALUE, true);
57 MAPPER.configure(SerializationConfig.Feature.INDENT_OUTPUT, true);
58 }
59
60
61
62
63
64 public static String toString(final CachedBlock cb, final long now) {
65 return "filename=" + cb.getFilename() + ", " + toStringMinusFileName(cb, now);
66 }
67
68
69
70
71
72 static class CachedBlockCountsPerFile {
73 private int count = 0;
74 private long size = 0;
75 private int countData = 0;
76 private long sizeData = 0;
77 private final String filename;
78
79 CachedBlockCountsPerFile(final String filename) {
80 this.filename = filename;
81 }
82
83 public int getCount() {
84 return count;
85 }
86
87 public long getSize() {
88 return size;
89 }
90
91 public int getCountData() {
92 return countData;
93 }
94
95 public long getSizeData() {
96 return sizeData;
97 }
98
99 public String getFilename() {
100 return filename;
101 }
102 }
103
104
105
106
107
108
109
110
111
112 public static String toJSON(final String filename, final NavigableSet<CachedBlock> blocks)
113 throws JsonGenerationException, JsonMappingException, IOException {
114 CachedBlockCountsPerFile counts = new CachedBlockCountsPerFile(filename);
115 for (CachedBlock cb: blocks) {
116 counts.count++;
117 counts.size += cb.getSize();
118 BlockType bt = cb.getBlockType();
119 if (bt != null && bt.isData()) {
120 counts.countData++;
121 counts.sizeData += cb.getSize();
122 }
123 }
124 return MAPPER.writeValueAsString(counts);
125 }
126
127
128
129
130
131
132
133
134 public static String toJSON(final CachedBlocksByFile cbsbf)
135 throws JsonGenerationException, JsonMappingException, IOException {
136 return MAPPER.writeValueAsString(cbsbf);
137 }
138
139
140
141
142
143
144
145
146 public static String toJSON(final BlockCache bc)
147 throws JsonGenerationException, JsonMappingException, IOException {
148 return MAPPER.writeValueAsString(bc);
149 }
150
151
152
153
154
155 public static String toStringMinusFileName(final CachedBlock cb, final long now) {
156 return "offset=" + cb.getOffset() +
157 ", size=" + cb.getSize() +
158 ", age=" + (now - cb.getCachedTime()) +
159 ", type=" + cb.getBlockType() +
160 ", priority=" + cb.getBlockPriority();
161 }
162
163
164
165
166
167
168
169
170 public static CachedBlocksByFile getLoadedCachedBlocksByFile(final Configuration conf,
171 final BlockCache bc) {
172 CachedBlocksByFile cbsbf = new CachedBlocksByFile(conf);
173 for (CachedBlock cb: bc) {
174 if (cbsbf.update(cb)) break;
175 }
176 return cbsbf;
177 }
178
179 private static int compareCacheBlock(Cacheable left, Cacheable right,
180 boolean includeNextBlockOnDiskSize) {
181 ByteBuffer l = ByteBuffer.allocate(left.getSerializedLength());
182 left.serialize(l, includeNextBlockOnDiskSize);
183 ByteBuffer r = ByteBuffer.allocate(right.getSerializedLength());
184 right.serialize(r, includeNextBlockOnDiskSize);
185 return Bytes.compareTo(l.array(), l.arrayOffset(), l.limit(),
186 r.array(), r.arrayOffset(), r.limit());
187 }
188
189
190
191
192
193
194
195
196
197
198 public static int validateBlockAddition(Cacheable existing, Cacheable newBlock,
199 BlockCacheKey cacheKey) {
200 int comparison = compareCacheBlock(existing, newBlock, false);
201 if (comparison != 0) {
202 throw new RuntimeException(
203 "Cached block contents differ, which should not have happened." + "cacheKey:" + cacheKey);
204 }
205 if ((existing instanceof HFileBlock) && (newBlock instanceof HFileBlock)) {
206 comparison = ((HFileBlock) existing).getNextBlockOnDiskSize()
207 - ((HFileBlock) newBlock).getNextBlockOnDiskSize();
208 }
209 return comparison;
210 }
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226 public static boolean shouldReplaceExistingCacheBlock(BlockCache blockCache,
227 BlockCacheKey cacheKey, Cacheable newBlock) {
228 Cacheable existingBlock = blockCache.getBlock(cacheKey, false, false, false);
229 int comparison = BlockCacheUtil.validateBlockAddition(existingBlock, newBlock, cacheKey);
230 if (comparison < 0) {
231 LOG.warn("Cached block contents differ by nextBlockOnDiskSize, the new block has "
232 + "nextBlockOnDiskSize set. Caching new block.");
233 return true;
234 } else if (comparison > 0) {
235 LOG.warn("Cached block contents differ by nextBlockOnDiskSize, the existing block has "
236 + "nextBlockOnDiskSize set, Keeping cached block.");
237 return false;
238 } else {
239 LOG.warn("Caching an already cached block: " + cacheKey
240 + ". This is harmless and can happen in rare " + "cases (see HBASE-8547)");
241 return false;
242 }
243 }
244
245
246
247
248
249
250 @JsonIgnoreProperties({"cachedBlockStatsByFile"})
251 public static class CachedBlocksByFile {
252 private int count;
253 private int dataBlockCount;
254 private long size;
255 private long dataSize;
256 private final long now = System.nanoTime();
257
258
259
260
261
262
263
264 private final int max;
265 public static final int DEFAULT_MAX = 1000000;
266
267 CachedBlocksByFile() {
268 this(null);
269 }
270
271 CachedBlocksByFile(final Configuration c) {
272 this.max = c == null? DEFAULT_MAX: c.getInt("hbase.ui.blockcache.by.file.max", DEFAULT_MAX);
273 }
274
275
276
277
278 private NavigableMap<String, NavigableSet<CachedBlock>> cachedBlockByFile =
279 new ConcurrentSkipListMap<String, NavigableSet<CachedBlock>>();
280 FastLongHistogram hist = new FastLongHistogram();
281
282
283
284
285
286 public boolean update(final CachedBlock cb) {
287 if (isFull()) return true;
288 NavigableSet<CachedBlock> set = this.cachedBlockByFile.get(cb.getFilename());
289 if (set == null) {
290 set = new ConcurrentSkipListSet<CachedBlock>();
291 this.cachedBlockByFile.put(cb.getFilename(), set);
292 }
293 set.add(cb);
294 this.size += cb.getSize();
295 this.count++;
296 BlockType bt = cb.getBlockType();
297 if (bt != null && bt.isData()) {
298 this.dataBlockCount++;
299 this.dataSize += cb.getSize();
300 }
301 long age = (this.now - cb.getCachedTime())/NANOS_PER_SECOND;
302 this.hist.add(age, 1);
303 return false;
304 }
305
306
307
308
309
310
311 public boolean isFull() {
312 return this.count >= this.max;
313 }
314
315 public NavigableMap<String, NavigableSet<CachedBlock>> getCachedBlockStatsByFile() {
316 return this.cachedBlockByFile;
317 }
318
319
320
321
322 public int getCount() {
323 return count;
324 }
325
326 public int getDataCount() {
327 return dataBlockCount;
328 }
329
330
331
332
333 public long getSize() {
334 return size;
335 }
336
337
338
339
340 public long getDataSize() {
341 return dataSize;
342 }
343
344 public AgeSnapshot getAgeInCacheSnapshot() {
345 return new AgeSnapshot(this.hist);
346 }
347
348 @Override
349 public String toString() {
350 AgeSnapshot snapshot = getAgeInCacheSnapshot();
351 return "count=" + count + ", dataBlockCount=" + dataBlockCount + ", size=" + size +
352 ", dataSize=" + getDataSize() +
353 ", mean age=" + snapshot.getMean() +
354 ", min age=" + snapshot.getMin() +
355 ", max age=" + snapshot.getMax() +
356 ", 75th percentile age=" + snapshot.get75thPercentile() +
357 ", 95th percentile age=" + snapshot.get95thPercentile() +
358 ", 98th percentile age=" + snapshot.get98thPercentile() +
359 ", 99th percentile age=" + snapshot.get99thPercentile() +
360 ", 99.9th percentile age=" + snapshot.get99thPercentile();
361 }
362 }
363 }