/*
* Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.sun.btrace.profiling;
import com.sun.btrace.Profiler;
import java.util.Arrays;
import java.util.Deque;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.LockSupport;
/**
* An invocation recorder class. All the invocations must be coming from the
* same thread (eg. by making a MethodInvocationRecorder instance thread local).
* <p>
* The only time multithreaded access must be resolved is when a snapshot of the
* measured data is being externally requested or the recorder is to be reset.
* <p>
* For this an atomic state variable is introduced to prevent simultaneous
* invocation processing and generating snapshot/resetting.
*
* @author Jaroslav Bachorik
*/
class MethodInvocationRecorder {
private static class DelayedRecord {
private final String blockName;
private final long duration;
public DelayedRecord(String blockName, long duration) {
this.blockName = blockName;
this.duration = duration;
}
}
private int stackSize = 200;
private int stackPtr = -1;
private int stackBndr = 150;
private Profiler.Record[] stackArr = new Profiler.Record[stackSize];
private int measuredSize = 0;
private int measuredPtr = 0;
private Profiler.Record[] measured = new Profiler.Record[0];
private long carryOver = 0L;
private final int defaultBufferSize;
private final Map<String, Integer> indexMap = new HashMap<String, Integer>();
private volatile int lastIndex = 0;
// 0 - available; 1 - processing invocation; 2 - generating snapshot; 3 - resetting
private final AtomicInteger writerStatus = new AtomicInteger(0);
private final Deque<DelayedRecord> delayedRecords = new LinkedList<DelayedRecord>();
public MethodInvocationRecorder(int expectedBlockCnt) {
defaultBufferSize = expectedBlockCnt << 8;
measuredSize = defaultBufferSize;
measured = new Profiler.Record[measuredSize];
}
void recordEntry(String blockName) {
while (true) {
processDelayedRecords();
if (writerStatus.compareAndSet(0, 1)) {
//System.out.println("== 0->1");
try {
processEntry(blockName);
return;
} finally {
//System.out.println("== 1->0");
writerStatus.compareAndSet(1, 0);
}
} else {
while (writerStatus.get() == 3) {
LockSupport.parkNanos(this, 600);
}
if (writerStatus.compareAndSet(1, 3)) {
//System.out.println("== 1->3");
try {
delayedRecords.add(new DelayedRecord(blockName, -1L));
return;
} finally {
//System.out.println("== 3->1");
writerStatus.compareAndSet(3, 1);
}
} else if (writerStatus.compareAndSet(2, 3)) {
//System.out.println("== 2->3");
try {
delayedRecords.add(new DelayedRecord(blockName, -1L));
return;
} finally {
//System.out.println("== 3->2");
writerStatus.compareAndSet(3, 2);
}
}
}
LockSupport.parkNanos(this, 600);
}
}
private void processEntry(String blockName) {
Profiler.Record r = new Profiler.Record(blockName);
addMeasured(r);
push(r);
carryOver = 0L; // clear the carryOver; not 2 subsequent calls to recordExit
}
void recordExit(String blockName, long duration) {
while (true) {
processDelayedRecords();
if (writerStatus.compareAndSet(0, 1)) {
//System.out.println("== 0->1");
try {
processExit(blockName, duration);
return;
} finally {
//System.out.println("== 1->0");
writerStatus.compareAndSet(1, 0);
}
} else {
while (writerStatus.get() == 3) {
LockSupport.parkNanos(this, 600);
}
if (writerStatus.compareAndSet(1, 3)) {
//System.out.println("== 1->3");
try {
delayedRecords.add(new DelayedRecord(blockName, duration));
return;
} finally {
//System.out.println("== 3->1");
writerStatus.compareAndSet(3, 1);
}
} else if (writerStatus.compareAndSet(2, 3)) {
//System.out.println("== 2->3");
try {
delayedRecords.add(new DelayedRecord(blockName, duration));
return;
} finally {
//System.out.println("== 3->2");
writerStatus.compareAndSet(3, 2);
}
}
}
LockSupport.parkNanos(this, 600);
}
}
private void processExit(String blockName, long duration) {
Profiler.Record r = pop();
if (r == null) {
r = new Profiler.Record(blockName);
addMeasured(r);
}
r.wallTime = duration;
r.selfTime += duration - carryOver;
for (int i = 0; i < stackPtr; i++) {
if (stackArr[i].blockName.equals(blockName)) {
r.wallTime = 0;
break;
}
}
r.selfTimeMin = r.selfTimeMax = r.selfTime;
r.wallTimeMin = r.wallTimeMax = r.wallTime;
Profiler.Record parent = peek();
if (parent != null) {
parent.selfTime -= duration;
} else {
carryOver = duration;
}
}
private void processDelayedRecords() {
DelayedRecord dr = null;
while (!writerStatus.compareAndSet(0, 3)) {
LockSupport.parkNanos(this, 600);
}
//System.out.println("== 0->3");
try {
while ((dr = delayedRecords.poll()) != null) {
if (dr.duration == -1) {
processEntry(dr.blockName);
} else {
processExit(dr.blockName, dr.duration);
}
}
} finally {
//System.out.println("== 3->0");
writerStatus.compareAndSet(3, 0);
}
}
Profiler.Record[] getRecords(boolean reset) {
Profiler.Record[] recs = null;
try {
processDelayedRecords();
//System.out.println("== 0->2");
while (!writerStatus.compareAndSet(0, 2)) {
LockSupport.parkNanos(this, 600);
}
compactMeasured();
recs = new Profiler.Record[lastIndex];
// copy and detach the record array
for (int i = 0; i < recs.length; i++) {
Profiler.Record r = measured[i];
if (r != null) {
recs[i] = r.duplicate();
} else {
System.err.println("Unexpected NULL record at position " + i + "; ignoring");
}
}
return recs;
} finally {
//System.out.println("== 2->0");
while (!writerStatus.compareAndSet(2, 0)) {
LockSupport.parkNanos(this, 600);
}
}
}
private void push(Profiler.Record r) {
if (stackPtr > stackBndr) {
stackSize = (stackSize * 3) >> 1;
stackBndr = (stackBndr * 3) >> 1;
Profiler.Record[] newStack = new Profiler.Record[stackSize];
System.arraycopy(stackArr, 0, newStack, 0, stackPtr + 1);
stackArr = newStack;
}
stackArr[++stackPtr] = r;
r.onStack = true;
}
private Profiler.Record pop() {
Profiler.Record r = stackPtr > -1 ? stackArr[stackPtr--] : null;
if (r != null) {
r.onStack = false;
}
return r;
}
private Profiler.Record peek() {
return stackPtr > -1 ? stackArr[stackPtr] : null;
}
private void addMeasured(Profiler.Record r) {
if (measuredPtr == measuredSize) {
compactMeasured();
}
measured[measuredPtr++] = r;
}
void reset() {
Profiler.Record[] newMeasured = new Profiler.Record[defaultBufferSize + stackPtr + 1];
try {
while (!writerStatus.compareAndSet(0, 4)) {
LockSupport.parkNanos(this, 600);
}
//System.out.println("== 4->0");
if (stackPtr > -1) {
System.arraycopy(stackArr, 0, newMeasured, 0, stackPtr + 1);
}
Arrays.fill(stackArr, null);
indexMap.clear();
measuredPtr = stackPtr + 1;
measured = newMeasured;
measuredSize = measured.length;
lastIndex = measuredPtr;
carryOver = 0L;
} finally {
//System.out.println("== 4->0");
writerStatus.compareAndSet(4, 0);
}
}
private void compactMeasured() {
int lastMeasurePtr = lastIndex;
if (lastIndex >= measuredPtr) {
return;
}
for (int i = lastIndex; i < measuredPtr; i++) {
Profiler.Record m = measured[i];
if (!m.onStack) {
Integer newIndex = indexMap.get(m.blockName);
if (newIndex == null) {
newIndex = lastMeasurePtr++;
indexMap.put(m.blockName, newIndex);
measured[newIndex] = m;
} else {
Profiler.Record mr = measured[newIndex];
mr.selfTime += m.selfTime;
mr.wallTime += m.wallTime;
mr.invocations++;
mr.selfTimeMax = m.selfTime > mr.selfTimeMax ? m.selfTime : mr.selfTimeMax;
mr.selfTimeMin = m.selfTime < mr.selfTimeMin ? m.selfTime : mr.selfTimeMin;
mr.wallTimeMax = m.wallTime > mr.wallTimeMax ? m.wallTime : mr.wallTimeMax;
mr.wallTimeMin = m.wallTime < mr.wallTimeMin ? m.wallTime : mr.wallTimeMin;
m.referring = mr;
}
}
}
for (int j = 0; j < stackPtr; j++) {
// if the old ref is kept on stack replace it with the compacted ref
Profiler.Record mr = stackArr[j].referring;
if (mr != null) {
stackArr[j] = mr;
}
}
if ((lastMeasurePtr + stackPtr + 1) == measuredSize) {
int newMeasuredSize = ((int) (measuredSize * 5) >> 2) + (stackPtr + 1); // make room for the methods on the stack
if (newMeasuredSize == measuredSize) {
newMeasuredSize = (measuredSize << 2) + (stackPtr + 1); // make room for the methods on the stack
}
Profiler.Record[] newMeasured = new Profiler.Record[newMeasuredSize];
System.arraycopy(measured, 0, newMeasured, 0, lastMeasurePtr); // copy the compacted values
measured = newMeasured;
measuredSize = newMeasuredSize;
}
System.arraycopy(stackArr, 0, measured, lastMeasurePtr, stackPtr + 1); // add the not processed methods on the stack
measuredPtr = lastMeasurePtr + stackPtr + 1; // move the pointer behind the methods on the stack
lastIndex = lastMeasurePtr;
}
}