Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
package com.ajjpj.asysmon.measure;
import com.ajjpj.abase.collection.mutable.ArrayStack;
import com.ajjpj.asysmon.config.ASysMonConfig;
import com.ajjpj.asysmon.config.log.ASysMonLogger;
import com.ajjpj.asysmon.data.ACorrelationId;
import com.ajjpj.asysmon.data.AHierarchicalData;
import com.ajjpj.asysmon.data.AHierarchicalDataRoot;
import com.ajjpj.asysmon.datasink.ADataSink;
import java.util.*;
/**
* This class collects a tree of hierarchical measurements. It lives in a single thread.
*
* @author arno
*/
public class AMeasurementHierarchyImpl implements AMeasurementHierarchy {
/**
* This is the number of nested measurements currently supported - when this level is reached, A-SysMon assumes
* there is a memory leak (i.e. there are measurements that are started but never finished) and kills the
* measurement hierarchy.
*/
public static final int MAX_CALL_DEPTH = 100;
private static final ASysMonLogger log = ASysMonLogger.get(AMeasurementHierarchyImpl.class);
private final ASysMonConfig config;
private final ADataSink dataSink;
private Set collectingMeasurements = new HashSet<>();
private final ArrayStack unfinished = new ArrayStack<>();
private final ArrayStack> childrenStack = new ArrayStack<>();
private final Collection startedFlows = new HashSet<>();
private final Collection joinedFlows = new HashSet<>();
/**
* shows if this measurement was finished in an orderly fashion
*/
private boolean isFinished = false;
/**
* shows if this measurement was killed forcibly e.g. because there was an overflow on the stack
*/
private boolean wasKilled = false;
public AMeasurementHierarchyImpl(ASysMonConfig config, ADataSink dataSink) {
this.config = config;
this.dataSink = dataSink;
}
private void checkNotFinished() {
if(isFinished) {
throw new IllegalStateException("This measurement is already closed.");
}
}
@Override public ASimpleMeasurement start(String identifier, boolean isSerial) {
if(config.isGloballyDisabled()) {
return new ASimpleMeasurement() {
@Override public void finish() {
}
@Override public void addParameter(String identifier, String value) {
}
};
}
checkNotFinished();
if(unfinished.isEmpty()) {
dataSink.onStartedHierarchicalMeasurement(identifier);
}
if(isSerial) {
checkOverflow();
final ASimpleSerialMeasurementImpl result = new ASimpleSerialMeasurementImpl(this, config.timer.getCurrentNanos(), identifier);
unfinished.push(result);
childrenStack.push(new ArrayList());
return result;
}
else {
return new ASimpleParallelMeasurementImpl(this, config.timer.getCurrentNanos(), identifier, childrenStack.peek());
}
}
private void checkOverflow() {
if(unfinished.size() < MAX_CALL_DEPTH) {
return;
}
ASimpleSerialMeasurementImpl rootMeasurement = null;
while(unfinished.nonEmpty()) {
rootMeasurement = unfinished.peek();
finish(unfinished.peek());
}
log.error("Detected probably memory leak, forcefully cleaning measurement stack. Root measurement was " + rootMeasurement.getIdentifier() + " with parameters " + rootMeasurement.getParameters() + ", started at " + new Date(rootMeasurement.getStartTimeMillis()));
wasKilled = true;
}
private void logWasKilled() {
log.warn("Interacting with a forcefully killed measurement. This is a consequence of A-SysMon cleaning up a (suspected) memory leak. It has no consequences aside from potentially weird measurements being reported.");
}
@Override public void finish(ASimpleSerialMeasurementImpl measurement) {
if(ASysMonConfig.isGloballyDisabled()) {
return;
}
if(wasKilled) {
logWasKilled();
return;
}
checkNotFinished();
if (unfinished.peek() != measurement) {
// This is basically a bug in using code: a measurement is 'finished' without being the innermost measurement
// of this hierarchy. The most typical reason for this - and the only one we can recover from - is that
// using code skipped finishing an inner measurement and is now finishing something further outside.
if(unfinished.contains(measurement)) {
log.warn("Calling 'finish' on a measurement " + measurement + " that is not innermost on the stack.");
while(unfinished.peek() != measurement) {
log.warn("-> Implicitly unrolling the stack of open measurements: " + unfinished.peek());
finish(unfinished.peek());
}
}
else {
throw new IllegalStateException("Calling 'finish' on a measurement that is not on the measurement stack: " + measurement);
}
}
final long finishedTimestamp = config.timer.getCurrentNanos();
unfinished.pop();
final List children = childrenStack.pop();
final AHierarchicalData newData = new AHierarchicalData(true, measurement.getStartTimeMillis(), finishedTimestamp - measurement.getStartTimeNanos(), measurement.getIdentifier(), measurement.getParameters(), children);
if(unfinished.isEmpty()) {
// copy into a separate collection because the collection is modified in the loop
for(ACollectingMeasurement m: new ArrayList<>(collectingMeasurements)) {
finish(m);
}
isFinished = true;
dataSink.onFinishedHierarchicalMeasurement(new AHierarchicalDataRoot(newData, startedFlows, joinedFlows));
}
else {
childrenStack.peek().add(newData);
}
}
@Override public void finish(ASimpleParallelMeasurementImpl m) {
if(ASysMonConfig.isGloballyDisabled()) {
return;
}
if(wasKilled) {
logWasKilled();
return;
}
checkNotFinished();
final long finishedTimestamp = config.timer.getCurrentNanos();
m.getChildrenOfParent().add(new AHierarchicalData(false, m.getStartTimeMillis(), finishedTimestamp - m.getStartTimeNanos(), m.getIdentifier(), m.getParameters(), Collections.emptyList()));
}
@Override
public ACollectingMeasurement startCollectingMeasurement(String identifier, boolean isSerial) {
if(ASysMonConfig.isGloballyDisabled()) {
return new ACollectingMeasurement(null, null, true, null, null);
}
checkNotFinished();
if(unfinished.isEmpty()) {
// A collection measurement can never be top-level. So we wrap a SimpleMeasurement around it, finishing it immediately
// to be on the safe side with regard to memory leaks
final ASimpleMeasurement m = start(IDENT_SYNTHETIC_ROOT, true);
try {
log.warn("starting a top-level collecting measurement - creating synthetic SimpleMeasurement and finishing immediately");
final ACollectingMeasurement result = new ACollectingMeasurement(config, this, isSerial, identifier, childrenStack.peek());
collectingMeasurements.add(result);
result.finish();
return result;
}
finally {
m.finish();
}
}
else {
final ACollectingMeasurement result = new ACollectingMeasurement(config, this, isSerial, identifier, childrenStack.peek());
collectingMeasurements.add(result);
return result;
}
}
@Override public void finish(ACollectingMeasurement m) {
if(ASysMonConfig.isGloballyDisabled()) {
return;
}
if(wasKilled) {
logWasKilled();
return;
}
checkNotFinished();
final List children = new ArrayList();
for(String detailIdentifier: m.getDetails().keySet()) {
final ACollectingMeasurement.Detail detail = m.getDetails().get(detailIdentifier);
children.add(new AHierarchicalData(true, m.getStartTimeMillis(), detail.getTotalNanos(), detailIdentifier, Collections.emptyMap(), Collections.emptyList()));
}
final AHierarchicalData newData = new AHierarchicalData(m.isSerial(), m.getStartTimeMillis(), m.getTotalDurationNanos(), m.getIdentifier(), m.getParameters(), children);
m.getChildrenOfParent().add(newData);
collectingMeasurements.remove(m);
}
/**
* notifies that this measurements contains the start of a new 'flow', i.e. it is the first point in a (potential) chain
* of measurements that are somehow correlated.
*/
@Override public void onStartFlow(ACorrelationId correlationId) {
if(!startedFlows.add(correlationId)) {
log.warn("called 'startFlow' for flow " + correlationId + " twice");
}
}
/**
* notifies that this measurements is part of an existing 'flow', i.e. there is another measurement that 'started' a
* set of measurements that are somehow correlated.
*/
@Override public void onJoinFlow(ACorrelationId correlationId) {
if(!joinedFlows.add(correlationId)) {
log.warn("called 'joinFlow' for flow " + correlationId + " twice");
}
}
}