/*
* Copyright (C) 2015 SoftIndex LLC.
*
* Licensed 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 io.datakernel.bytebuf;
import io.datakernel.util.ConcurrentStack;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import static io.datakernel.bytebuf.ByteBufRegistry.ByteBufMetaInfo;
import static io.datakernel.bytebuf.ByteBufRegistry.ByteBufWrapper;
import static java.lang.Integer.numberOfLeadingZeros;
public final class ByteBufPool {
private static final int NUMBER_SLABS = 33;
private static int minSize = 32;
private static int maxSize = 1 << 30;
private static final ConcurrentStack<ByteBuf>[] slabs = createSlabs(NUMBER_SLABS);
private static final AtomicInteger[] created = createCounters(NUMBER_SLABS);
private ByteBufPool() {}
public static ByteBuf allocate(int size) {
if (size < minSize || size >= maxSize) {
// not willing to register in pool
return ByteBuf.wrapForWriting(new byte[size]);
}
int index = 32 - numberOfLeadingZeros(size - 1); // index==32 for size==0
ConcurrentStack<ByteBuf> queue = slabs[index];
ByteBuf buf = queue.pop();
if (buf != null) {
buf.reset();
} else {
buf = ByteBuf.wrapForWriting(new byte[1 << index]);
buf.refs++;
assert created[index].incrementAndGet() != 0;
}
assert ByteBufRegistry.recordAllocate(buf);
return buf;
}
public static void recycle(ByteBuf buf) {
assert buf.array.length >= minSize && buf.array.length <= maxSize;
ConcurrentStack<ByteBuf> queue = slabs[32 - numberOfLeadingZeros(buf.array.length - 1)];
assert !queue.contains(buf) : "duplicate recycle array";
queue.push(buf);
assert ByteBufRegistry.recordRecycle(buf);
}
public static ByteBuf recycleIfEmpty(ByteBuf buf) {
if (buf.canRead())
return buf;
buf.recycle();
return ByteBuf.empty();
}
public static ConcurrentStack<ByteBuf>[] getPool() {
return slabs;
}
public static void clear() {
for (int i = 0; i < ByteBufPool.NUMBER_SLABS; i++) {
slabs[i].clear();
created[i].set(0);
}
}
private static ConcurrentStack<ByteBuf>[] createSlabs(int numberOfSlabs) {
//noinspection unchecked
ConcurrentStack<ByteBuf>[] slabs = new ConcurrentStack[numberOfSlabs];
for (int i = 0; i < slabs.length; i++) {
slabs[i] = new ConcurrentStack<>();
}
return slabs;
}
private static AtomicInteger[] createCounters(int amount) {
AtomicInteger[] counters = new AtomicInteger[amount];
for (int i = 0; i < counters.length; i++) {
counters[i] = new AtomicInteger(0);
}
return counters;
}
public static ByteBuf ensureTailRemaining(ByteBuf buf, int newTailRemaining) {
assert !(buf instanceof ByteBuf.ByteBufSlice);
if (buf.writeRemaining() >= newTailRemaining) {
return buf;
} else {
ByteBuf newBuf = allocate(newTailRemaining + buf.readRemaining());
newBuf.put(buf);
buf.recycle();
return newBuf;
}
}
public static ByteBuf append(ByteBuf to, ByteBuf from) {
assert !to.isRecycled() && !from.isRecycled();
if (to.readRemaining() == 0) {
to.recycle();
return from;
}
to = ensureTailRemaining(to, from.readRemaining());
to.put(from);
from.recycle();
return to;
}
public static ByteBuf append(ByteBuf to, byte[] from, int offset, int length) {
assert !to.isRecycled();
to = ensureTailRemaining(to, length);
to.put(from, offset, length);
return to;
}
public static ByteBuf append(ByteBuf to, byte[] from) {
return append(to, from, 0, from.length);
}
private static final ByteBufPoolStats stats = new ByteBufPoolStats();
public static ByteBufPoolStats getStats() {
return stats;
}
public static int getCreatedItems() {
int items = 0;
for (AtomicInteger counter : created) {
items += counter.get();
}
return items;
}
public static int getCreatedItems(int slab) {
assert slab >= 0 && slab < slabs.length;
return created[slab].get();
}
public static int getPoolItems(int slab) {
assert slab >= 0 && slab < slabs.length;
return slabs[slab].size();
}
public static int getPoolItems() {
int result = 0;
for (ConcurrentStack<ByteBuf> slab : slabs) {
result += slab.size();
}
return result;
}
public static String getPoolItemsString() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < ByteBufPool.NUMBER_SLABS; ++i) {
int createdItems = ByteBufPool.getCreatedItems(i);
int poolItems = ByteBufPool.getPoolItems(i);
if (createdItems != poolItems) {
sb.append(String.format("Slab %d (%d) ", i, (1 << i)))
.append(" created: ").append(createdItems)
.append(" pool: ").append(poolItems).append("\n");
}
}
return sb.toString();
}
private static long getPoolSize() {
assert slabs.length == 33 : "Except slabs[32] that contains ByteBufs with size 0";
long result = 0;
for (int i = 0; i < slabs.length - 1; i++) {
long slotSize = 1L << i;
result += slotSize * slabs[i].size();
}
return result;
}
public static void setSizes(int minSize, int maxSize) {
ByteBufPool.minSize = minSize;
ByteBufPool.maxSize = maxSize;
}
public interface ByteBufPoolStatsMXBean {
int getCreatedItems();
int getPoolItems();
long getPoolItemAvgSize();
long getPoolSizeKB();
List<String> getPoolSlabs();
List<ByteBufJmxInfo> getOldestByteBufs_Details();
List<String> getOldestByteBufs_Summary();
boolean getOldestByteBufs_settings_StoreStackTrace();
void setOldestByteBufs_settings_StoreStackTrace(boolean flag);
boolean getOldestByteBufs_settings_StoreByteBufs();
void setOldestByteBufs_settings_StoreByteBufs(boolean flag);
int getOldestByteBufs_settings_MaxBytesInContent();
void setOldestByteBufs_settings_MaxBytesInContent(int bytes);
int getOldestByteBufs_settings_MaxByteBufsToShow();
void setOldestByteBufs_settings_MaxByteBufsToShow(int bufs);
int getTotalActiveByteBufs();
long getBufs_TotalAllocated();
long getBufs_TotalRecycled();
long getBufs_TotalNotRecycled();
long getBytes_TotalAllocated();
long getBytes_TotalRecycled();
long getBytes_TotalNotRecycled();
void clearRegistry();
ByteBufDetailedJmxInfo fetchDetailedByteBufInfo(int indexInList, int start, int to);
}
public static final class ByteBufJmxInfo {
private final long duration;
private final List<String> stackTrace;
private final int size;
private final int readPosition;
private final int writePosition;
private final String content;
public ByteBufJmxInfo(long duration, List<String> stackTrace, int size, int readPosition, int writePosition, String content) {
this.duration = duration;
this.stackTrace = stackTrace;
this.size = size;
this.readPosition = readPosition;
this.writePosition = writePosition;
this.content = content;
}
public String getDuration() {
return formatDuration(duration);
}
public List<String> getStackTrace() {
return stackTrace;
}
public int getSize() {
return size;
}
public int getReadPosition() {
return readPosition;
}
public int getWritePosition() {
return writePosition;
}
public String getContent() {
return content;
}
}
public static final class ByteBufDetailedJmxInfo {
private final long duration;
private final List<String> stackTrace;
private final int size;
private final int readPosition;
private final int writePosition;
private final String content;
private final int queriedFirstByteIndex;
private final int queriedLastByteIndex;
private final String queriedBytes;
private final String queriedBytesHex;
public ByteBufDetailedJmxInfo(long duration, List<String> stackTrace, int size,
int readPosition, int writePosition, String content,
int queriedFirstByteIndex, int queriedLastByteIndex,
String queriedBytes, String queriedBytesHex) {
this.duration = duration;
this.stackTrace = stackTrace;
this.size = size;
this.readPosition = readPosition;
this.writePosition = writePosition;
this.content = content;
this.queriedFirstByteIndex = queriedFirstByteIndex;
this.queriedLastByteIndex = queriedLastByteIndex;
this.queriedBytes = queriedBytes;
this.queriedBytesHex = queriedBytesHex;
}
public long getDuration() {
return duration;
}
public List<String> getStackTrace() {
return stackTrace;
}
public int getSize() {
return size;
}
public int getReadPosition() {
return readPosition;
}
public int getWritePosition() {
return writePosition;
}
public String getContent() {
return content;
}
public int getQueriedFirstByteIndex() {
return queriedFirstByteIndex;
}
public int getQueriedLastByteIndex() {
return queriedLastByteIndex;
}
public String getQueriedBytes() {
return queriedBytes;
}
public String getQueriedBytesHex() {
return queriedBytesHex;
}
}
public static final class ByteBufPoolStats implements ByteBufPoolStatsMXBean {
private volatile int maxBytesInContent = 25;
private volatile int maxBufsToShow = 100;
@Override
public int getCreatedItems() {
return ByteBufPool.getCreatedItems();
}
@Override
public int getPoolItems() {
return ByteBufPool.getPoolItems();
}
@Override
public long getPoolItemAvgSize() {
int result = 0;
for (ConcurrentStack<ByteBuf> slab : slabs) {
result += slab.size();
}
int items = result;
return items == 0 ? 0 : ByteBufPool.getPoolSize() / items;
}
@Override
public long getPoolSizeKB() {
return ByteBufPool.getPoolSize() / 1024;
}
@Override
public List<String> getPoolSlabs() {
assert slabs.length == 33 : "Except slabs[32] that contains ByteBufs with size 0";
List<String> result = new ArrayList<>(slabs.length + 1);
result.add("SlotSize,Created,InPool,Total(Kb)");
for (int i = 0; i < slabs.length; i++) {
long slotSize = 1L << i;
int count = slabs[i].size();
result.add((slotSize & 0xffffffffL) + "," + created[i] + "," + count + "," + slotSize * count / 1024);
}
return result;
}
@Override
public List<ByteBufJmxInfo> getOldestByteBufs_Details() {
Map<ByteBufWrapper, ByteBufMetaInfo> activeBufs = ByteBufRegistry.getActiveByteBufs();
List<ByteBufJmxInfo> bufsInfo = new ArrayList<>();
long currentTimestamp = System.currentTimeMillis();
int maxBufsToShowCached = maxBufsToShow;
for (ByteBufWrapper wrapper : activeBufs.keySet()) {
if (bufsInfo.size() == maxBufsToShowCached) {
break;
}
ByteBufMetaInfo byteBufMetaInfo = activeBufs.get(wrapper);
if (byteBufMetaInfo == null) {
continue;
}
ByteBuf buf = wrapper.getByteBuf();
if (buf == null) {
continue;
}
long duration = currentTimestamp - byteBufMetaInfo.getAllocationTimestamp();
List<String> stackTraceLines = fetchStackTrace(byteBufMetaInfo);
String content = extractContent(buf, maxBytesInContent);
ByteBufJmxInfo byteBufJmxInfo = new ByteBufJmxInfo(duration, stackTraceLines,
buf.limit(), buf.readPosition(), buf.writePosition(), content);
bufsInfo.add(byteBufJmxInfo);
}
Collections.sort(bufsInfo, new Comparator<ByteBufJmxInfo>() {
@Override
public int compare(ByteBufJmxInfo o1, ByteBufJmxInfo o2) {
return -(Long.compare(o1.duration, o2.duration));
}
});
return bufsInfo;
}
@Override
public List<String> getOldestByteBufs_Summary() {
List<ByteBufJmxInfo> detailedInfo = getOldestByteBufs_Details();
List<String> summaryLines = new ArrayList<>();
summaryLines.add("Duration Content");
for (ByteBufJmxInfo info : detailedInfo) {
summaryLines.add(String.format("%s %s", info.getDuration(), info.getContent()));
}
return summaryLines;
}
@Override
public boolean getOldestByteBufs_settings_StoreStackTrace() {
return ByteBufRegistry.getStoreStackTrace();
}
@Override
public void setOldestByteBufs_settings_StoreStackTrace(boolean flag) {
ByteBufRegistry.setStoreStackTrace(flag);
}
@Override
public boolean getOldestByteBufs_settings_StoreByteBufs() {
return ByteBufRegistry.getStoreByteBufs();
}
@Override
public void setOldestByteBufs_settings_StoreByteBufs(boolean flag) {
ByteBufRegistry.setStoreByteBufs(flag);
}
@Override
public int getOldestByteBufs_settings_MaxBytesInContent() {
return maxBytesInContent;
}
@Override
public void setOldestByteBufs_settings_MaxBytesInContent(int bytesInContent) {
if (bytesInContent < 0) {
throw new IllegalArgumentException("argument must be non-negative");
}
this.maxBytesInContent = bytesInContent;
}
@Override
public int getOldestByteBufs_settings_MaxByteBufsToShow() {
return maxBufsToShow;
}
@Override
public void setOldestByteBufs_settings_MaxByteBufsToShow(int bufs) {
if (bufs < 0) {
throw new IllegalArgumentException("argument must be non-negative");
}
this.maxBufsToShow = bufs;
}
@Override
public int getTotalActiveByteBufs() {
return ByteBufRegistry.getActiveByteBufs().size();
}
@Override
public long getBufs_TotalAllocated() {
return ByteBufRegistry.getTotalAllocatedBufs();
}
@Override
public long getBufs_TotalRecycled() {
return ByteBufRegistry.getTotalRecycledBufs();
}
@Override
public long getBufs_TotalNotRecycled() {
return ByteBufRegistry.getTotalAllocatedBufs() - ByteBufRegistry.getTotalRecycledBufs();
}
@Override
public long getBytes_TotalAllocated() {
return ByteBufRegistry.getTotalAllocatedBytes();
}
@Override
public long getBytes_TotalRecycled() {
return ByteBufRegistry.getTotalRecycledBytes();
}
@Override
public long getBytes_TotalNotRecycled() {
return ByteBufRegistry.getTotalAllocatedBytes() - ByteBufRegistry.getTotalRecycledBytes();
}
@Override
public void clearRegistry() {
ByteBufRegistry.clearRegistry();
}
@Override
public ByteBufDetailedJmxInfo fetchDetailedByteBufInfo(int indexInList, int start, int to) {
Map<ByteBufWrapper, ByteBufMetaInfo> activeBufs = ByteBufRegistry.getActiveByteBufs();
List<ByteBufDetailedJmxInfo> bufsInfo = new ArrayList<>();
long currentTimestamp = System.currentTimeMillis();
int maxBufsToShowCached = maxBufsToShow;
for (ByteBufWrapper wrapper : activeBufs.keySet()) {
if (bufsInfo.size() == maxBufsToShowCached) {
break;
}
ByteBufMetaInfo byteBufMetaInfo = activeBufs.get(wrapper);
if (byteBufMetaInfo == null) {
continue;
}
ByteBuf buf = wrapper.getByteBuf();
if (buf == null) {
continue;
}
long duration = currentTimestamp - byteBufMetaInfo.getAllocationTimestamp();
List<String> stackTraceLines = fetchStackTrace(byteBufMetaInfo);
String content = extractContent(buf, maxBytesInContent);
byte[] queriedBytes = Arrays.copyOfRange(buf.array(), start, to);
String queriedBytesStr = new String(queriedBytes);
StringBuilder queriedBytesHex = new StringBuilder(queriedBytes.length * 3);
for (byte queriedByte : queriedBytes) {
queriedBytesHex.append(byteToHex(queriedByte));
queriedBytesHex.append(" ");
}
ByteBufDetailedJmxInfo byteBufJmxInfo = new ByteBufDetailedJmxInfo(duration, stackTraceLines,
buf.limit(), buf.readPosition(), buf.writePosition(), content,
start, to, queriedBytesStr, queriedBytesHex.toString());
bufsInfo.add(byteBufJmxInfo);
}
Collections.sort(bufsInfo, new Comparator<ByteBufDetailedJmxInfo>() {
@Override
public int compare(ByteBufDetailedJmxInfo o1, ByteBufDetailedJmxInfo o2) {
return -(Long.compare(o1.duration, o2.duration));
}
});
return bufsInfo.get(indexInList);
}
private List<String> fetchStackTrace(ByteBufMetaInfo byteBufMetaInfo) {
List<String> stackTraceLines = new ArrayList<>();
StackTraceElement[] stackTrace = byteBufMetaInfo.getStackTrace();
if (stackTrace != null) {
for (StackTraceElement stackTraceElement : stackTrace) {
stackTraceLines.add(stackTraceElement.toString());
}
}
return stackTraceLines;
}
private String byteToHex(byte b) {
return String.format("%02X", b);
}
}
private static String formatHours(long period) {
long milliseconds = period % 1000;
long seconds = (period / 1000) % 60;
long minutes = (period / (60 * 1000)) % 60;
long hours = period / (60 * 60 * 1000);
return String.format("%02d", hours) + ":" + String.format("%02d", minutes) + ":" + String.format("%02d", seconds) + "." + String.format("%03d", milliseconds);
}
public static String formatDuration(long period) {
if (period == 0)
return "";
return formatHours(period);
}
private static String extractContent(ByteBuf buf, int maxSize) {
int to = buf.readPosition() + Math.min(maxSize, buf.readRemaining());
return new String(Arrays.copyOfRange(buf.array(), buf.readPosition(), to));
}
//endregion
}