package org.sef4j.callstack.stats;
import java.util.concurrent.Callable;
import java.util.function.Predicate;
import org.sef4j.core.util.ICopySupport;
/**
* performance time statistics and counters using small Log-based histogram
* <BR/>
* This class is multi-thread safe, and lock FREE !!
* <BR/>
*
* Ranges are hard-coded with 10 buckets, using this logarithmic range:
* <ul>
* <li> [0]: 0 millis</li>
* <li> [1]: 1 - 31 millis</li>
* <li> [2]: 32 - 63 millis</li>
* <li> [3]: 64 - 127 millis</li>
* <li> [4]: 128 - 255 millis</li>
* <li> [5]: 256 - 511 millis</li>
* <li> [6]: 512 - 1023 millis</li>
* <li> [7]: 1024 - 2047 millis</li>
* <li> [8]: 2048 - 4095 millis</li>
* <li> [9]: more than 4096 millis</li>
* </ul>
*/
@SuppressWarnings("restriction")
public final class BasicTimeStatsLogHistogram implements ICopySupport<BasicTimeStatsLogHistogram> {
/**
* slot count
*/
public static final int SLOT_LEN = 10;
/**
* occurrence count per elapsed time using histogram slots
*
* (values updated atomically using code similar to AtomicIntegerArray using UNSAFE.getAndAddInt() / .getIntVolatile()
* but optimized: without using wrapper class + extra array index bound checking
* )
*/
private int[] countSlots = new int[SLOT_LEN];
/**
* sum of calls elapsed time in nanos, using histogram slots
*
* (values updated atomically using code similar to AtomicLongArray using UNSAFE.getAndAddLong() / .getLongVolatile()
* but optimized: without using wrapper class + extra array index bound checking
* )
*/
private long[] sumSlots = new long[SLOT_LEN];
// ------------------------------------------------------------------------
public BasicTimeStatsLogHistogram() {
}
public BasicTimeStatsLogHistogram(BasicTimeStatsLogHistogram src) {
set(src);
}
public static final Callable<BasicTimeStatsLogHistogram> FACTORY = new Callable<BasicTimeStatsLogHistogram>() {
@Override
public BasicTimeStatsLogHistogram call() throws Exception {
return new BasicTimeStatsLogHistogram();
}
};
public static class MinCountPropTreeValuePredicate implements Predicate<BasicTimeStatsLogHistogram> {
public static final MinCountPropTreeValuePredicate INSTANCE = new MinCountPropTreeValuePredicate(0, 0);
private final int minCount;
private final long minSum;
public MinCountPropTreeValuePredicate(int minCount, long minSum) {
this.minCount = minCount;
this.minSum = minSum;
}
@Override
public boolean test(BasicTimeStatsLogHistogram src) {
int cumulCount = src.cumulatedCount();
long cumulSum = src.cumulatedSum();
return cumulCount > minCount || cumulSum > minSum;
}
}
// ------------------------------------------------------------------------
public int getCount(int index) {
assert index >= 0 && index < SLOT_LEN;
return UNSAFE.getIntVolatile(countSlots, byteOffsetInt(index));
}
public long getSum(int index) {
assert index >= 0 && index < SLOT_LEN;
return UNSAFE.getLongVolatile(sumSlots, byteOffsetLong(index));
}
public void incr(long value) {
int index = valueToSlotIndex(value);
UNSAFE.getAndAddInt(countSlots, byteOffsetInt(index), 1);
UNSAFE.getAndAddLong(sumSlots, byteOffsetLong(index), value);
}
public void incr(BasicTimeStatsLogHistogram src) {
for (int i = 0; i < SLOT_LEN; i++) {
UNSAFE.getAndAddInt(countSlots, byteOffsetInt(i), src.getCount(i));
UNSAFE.getAndAddLong(sumSlots, byteOffsetLong(i), src.getSum(i));
}
}
public void set(BasicTimeStatsLogHistogram src) {
// no atomic sync, TOCHANGE: may use array copy?
for (int i = 0; i < SLOT_LEN; i++) {
this.countSlots[i] = src.getCount(i);
this.sumSlots[i] = src.getSum(i);
}
}
// ------------------------------------------------------------------------
/** @return sum of values in all slots */
public long cumulatedSum() {
long res = 0;
for (int i = 0; i < SLOT_LEN; i++) {
res += getSum(i);
}
return res;
}
/** @return sum of counts in all slots */
public int cumulatedCount() {
int res = 0;
for (int i = 0; i < SLOT_LEN; i++) {
res += getCount(i);
}
return res;
}
/** @return copy of all slots */
public BasicTimeStatsSlotInfo[] getSlotInfoCopy() {
BasicTimeStatsSlotInfo[] res = new BasicTimeStatsSlotInfo[SLOT_LEN];
for (int i = 0; i < SLOT_LEN; i++) {
BasicTimeStatsSlotInfo slotInfo = SLOT_INFOS[i];
res[i] = new BasicTimeStatsSlotInfo(slotInfo.getFrom(), slotInfo.getTo(), getCount(i), getSum(i));
}
return res;
}
/** @return copy of nth-slot */
public BasicTimeStatsSlotInfo getSlotInfoCopyAt(int i) {
if (i < 0 || i >= SLOT_LEN) throw new ArrayIndexOutOfBoundsException();
BasicTimeStatsSlotInfo slotInfo = SLOT_INFOS[i];
return new BasicTimeStatsSlotInfo(slotInfo.getFrom(), slotInfo.getTo(), getCount(i), getSum(i));
}
@Override /* java.lang.Object */
public BasicTimeStatsLogHistogram clone() {
return copy();
}
@Override /* ICopySupport<> */
public BasicTimeStatsLogHistogram copy() {
return new BasicTimeStatsLogHistogram(this);
}
public boolean compareHasChangeCount(BasicTimeStatsLogHistogram cmp) {
for (int i = 0; i < SLOT_LEN; i++) {
if (getCount(i) != cmp.getCount(i)) {
return true;
}
}
return false;
}
@Override
public String toString() {
long count = cumulatedCount();
long avg = (count != 0)? cumulatedSum()/count : 0;
return "PerfStatsHistogram ["
+ "count:" + count
+ ", avg:" + avg
+ "]";
}
// internal utilities for log-based index
// ------------------------------------------------------------------------
private static final BasicTimeStatsSlotInfo[] SLOT_INFOS;
private static final int MAX_SLOT_VALUE = 4096;
private static final int[] VALUE_DIV32_TO_SLOT_INDEX;
static {
int[] breaks = new int[] {
1, 32, 64, 128, 256, 512, 1024, 2048, 4096
};
BasicTimeStatsSlotInfo[] tmp = new BasicTimeStatsSlotInfo[SLOT_LEN];
int[] tmpValueToSlotIndex = new int[MAX_SLOT_VALUE/32];
tmp[0] = new BasicTimeStatsSlotInfo(-Long.MAX_VALUE, 0, 0, 0);
// System.out.println("0: [-INF, 0]");
int index = 1;
int from = 1;
for(int i = 1; i <= MAX_SLOT_VALUE; i++) {
if (breaks[index] == i) {
tmp[index] = new BasicTimeStatsSlotInfo(from, i-1, 0, 0);
// System.out.println(index + ": [" + from + ", " + (i-1) + "]");
index++;
from = i;
}
if (i == MAX_SLOT_VALUE) break;
int iDiv32 = i >>> 5;
tmpValueToSlotIndex[iDiv32] = index;
}
tmp[SLOT_LEN-1] = new BasicTimeStatsSlotInfo(from, Long.MAX_VALUE, 0, 0);
// System.out.println((SLOT_LEN-1) + ": [" + from + ", +INF]");
SLOT_INFOS = tmp;
VALUE_DIV32_TO_SLOT_INDEX = tmpValueToSlotIndex;
// check..
if (1 != valueToSlotIndex(30)) throw new IllegalStateException();
if (1 != valueToSlotIndex(31)) throw new IllegalStateException();
if (2 != valueToSlotIndex(32)) throw new IllegalStateException();
if (2 != valueToSlotIndex(33)) throw new IllegalStateException();
if (SLOT_LEN-2 != valueToSlotIndex(4095)) throw new IllegalStateException();
if (SLOT_LEN-1 != valueToSlotIndex(4096)) throw new IllegalStateException();
index = 1;
for(int i = 1; i < MAX_SLOT_VALUE; i++) {
if (breaks[index] == i) {
index++;
}
int checkSlot = valueToSlotIndex(i);
if (checkSlot != index) {
throw new IllegalStateException("ERROR " + i + " => " + checkSlot + " != " + index);
}
}
}
/**
* index using logarithm / linear by parts
*/
public static int valueToSlotIndex(long value) {
if (value <= 0) return 0;
else if (value >= MAX_SLOT_VALUE) return SLOT_LEN-1;
int v = (int) value;
if (v < 32) return 1;
else return VALUE_DIV32_TO_SLOT_INDEX[v >>> 5];
}
// internal utilities of UNSAFE memory access for atomic operation, without locks
// ------------------------------------------------------------------------
private static sun.misc.Unsafe UNSAFE;
private static final long ARRAY_BASE_OFFSET; // = 16...
private static final int shiftInt; // = 2 ...
private static final int shiftLong; // = 3 ...
static {
UNSAFE = UnsafeUtils.getUnsafe();
ARRAY_BASE_OFFSET = UNSAFE.arrayBaseOffset(byte[].class); // = 16...
int scaleInt = UNSAFE.arrayIndexScale(int[].class); // =4... size of an "int" in an int[] array
shiftInt = 31 - Integer.numberOfLeadingZeros(scaleInt);
int scaleLong = UNSAFE.arrayIndexScale(long[].class); // =8... size of a "long" in a long[] array
shiftLong = 31 - Integer.numberOfLeadingZeros(scaleLong);
}
private static long byteOffsetInt(int i) {
return ((long) i << shiftInt) + ARRAY_BASE_OFFSET;
}
private static long byteOffsetLong(int i) {
return ((long) i << shiftLong) + ARRAY_BASE_OFFSET;
}
}