/*
* 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 java.lang.ref.Reference;
import java.lang.ref.SoftReference;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
public final class ByteBufRegistry {
public static final int CLEAR_EMPTY_WRAPPERS_PERIOD = 1000;
private static final ConcurrentHashMap<ByteBufWrapper, ByteBufMetaInfo> activeByteBufs = new ConcurrentHashMap<>();
private static AtomicInteger counter = new AtomicInteger(0);
private static volatile boolean storeByteBufs = false;
private static volatile boolean storeStackTrace = false;
private static final AtomicLong totalAllocatedBufs = new AtomicLong(0);
private static final AtomicLong totalRecycledBufs = new AtomicLong(0);
private static final AtomicLong totalAllocatedBytes = new AtomicLong(0);
private static final AtomicLong totalRecycledBytes = new AtomicLong(0);
private ByteBufRegistry() {}
// region public api
public static void clearRegistry() {
activeByteBufs.clear();
}
public static Map<ByteBufWrapper, ByteBufMetaInfo> getActiveByteBufs() {
return activeByteBufs;
}
public static boolean getStoreStackTrace() {
return storeStackTrace;
}
public static void setStoreStackTrace(boolean store) {
storeStackTrace = store;
}
public static boolean getStoreByteBufs() {
return storeByteBufs;
}
public static void setStoreByteBufs(boolean store) {
storeByteBufs = store;
}
public static long getTotalAllocatedBufs() {
return totalAllocatedBufs.longValue();
}
public static long getTotalRecycledBufs() {
return totalRecycledBufs.longValue();
}
public static long getTotalAllocatedBytes() {
return totalAllocatedBytes.longValue();
}
public static long getTotalRecycledBytes() {
return totalRecycledBytes.longValue();
}
// endregion
public static boolean recordAllocate(ByteBuf buf) {
totalAllocatedBufs.incrementAndGet();
totalAllocatedBytes.addAndGet(buf.array().length);
if (storeByteBufs) {
int current = counter.incrementAndGet();
if (current % CLEAR_EMPTY_WRAPPERS_PERIOD == 0) { // in case of negative values it also works properly
clearEmptyWrappers();
}
StackTraceElement[] stackTrace = null;
if (storeStackTrace) {
// TODO(vmykhalko): maybe use new Exception().getStackTrace instead ? according to performance issues
StackTraceElement[] fullStackTrace = Thread.currentThread().getStackTrace();
// remove stack trace lines that stand for registration method calls
stackTrace = Arrays.copyOfRange(fullStackTrace, 3, fullStackTrace.length);
}
long timestamp = System.currentTimeMillis();
ByteBufMetaInfo metaInfo = new ByteBufMetaInfo(stackTrace, timestamp);
activeByteBufs.put(new ByteBufWrapper(buf), metaInfo);
}
return true;
}
public static boolean recordRecycle(ByteBuf buf) {
totalRecycledBufs.incrementAndGet();
totalRecycledBytes.addAndGet(buf.array().length);
if (storeByteBufs) {
activeByteBufs.remove(new ByteBufWrapper(buf));
}
return true;
}
private static void clearEmptyWrappers() {
Iterator<Map.Entry<ByteBufWrapper, ByteBufMetaInfo>> iterator = activeByteBufs.entrySet().iterator();
while (iterator.hasNext()) {
ByteBufWrapper wrapper = iterator.next().getKey();
if (wrapper.getByteBuf() == null) {
iterator.remove();
}
}
}
static final class ByteBufWrapper {
private final Reference<ByteBuf> bufRef;
public ByteBufWrapper(ByteBuf buf) {
this.bufRef = new SoftReference<>(buf);
}
public ByteBuf getByteBuf() {
return bufRef.get();
}
@Override
public boolean equals(Object o) {
if (o == null) {
return false;
}
if (o.getClass() != ByteBufWrapper.class) {
return false;
}
ByteBufWrapper other = (ByteBufWrapper) o;
return this.bufRef.get() == other.bufRef.get();
}
@Override
public int hashCode() {
return System.identityHashCode(bufRef.get());
}
}
static final class ByteBufMetaInfo {
private final StackTraceElement[] stackTrace;
private final long allocateTimestamp;
public ByteBufMetaInfo(StackTraceElement[] stackTrace, long allocateTimestamp) {
this.stackTrace = stackTrace;
this.allocateTimestamp = allocateTimestamp;
}
public StackTraceElement[] getStackTrace() {
return stackTrace;
}
public long getAllocationTimestamp() {
return allocateTimestamp;
}
}
}