package rocks.inspectit.agent.java.sensor.method.timer;
import java.lang.management.ThreadMXBean;
import java.sql.Timestamp;
import java.util.List;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import rocks.inspectit.agent.java.config.IPropertyAccessor;
import rocks.inspectit.agent.java.config.impl.RegisteredSensorConfig;
import rocks.inspectit.agent.java.core.ICoreService;
import rocks.inspectit.agent.java.core.IPlatformManager;
import rocks.inspectit.agent.java.hooking.IConstructorHook;
import rocks.inspectit.agent.java.hooking.IMethodHook;
import rocks.inspectit.agent.java.sensor.method.averagetimer.AverageTimerHook;
import rocks.inspectit.agent.java.util.StringConstraint;
import rocks.inspectit.agent.java.util.ThreadLocalStack;
import rocks.inspectit.agent.java.util.Timer;
import rocks.inspectit.shared.all.communication.data.ParameterContentData;
/**
* The hook implementation for the timer sensor. It uses the {@link ThreadLocalStack} class to save
* the time when the method was called.
* <p>
* The difference to the {@link AverageTimerHook} is that it's using {@link ITimerStorage} objects
* to save the values. The {@link ITimerStorage} is responsible for the actual data saving, so
* different strategies can be chosen from (set through the configuration file).
*
* @author Patrice Bouillet
*
*/
public class TimerHook implements IMethodHook, IConstructorHook {
/**
* The logger of this class. Initialized manually.
*/
private static final Logger LOG = LoggerFactory.getLogger(TimerHook.class);
/**
* The stack containing the start time values.
*/
private final ThreadLocalStack<Double> timeStack = new ThreadLocalStack<Double>();
/**
* The timer used for accurate measuring.
*/
private final Timer timer;
/**
* The Platform manager.
*/
private final IPlatformManager platformManager;
/**
* The property accessor.
*/
private final IPropertyAccessor propertyAccessor;
/**
* The timer storage factory which returns a new {@link ITimerStorage} object every time we
* request one. The returned storage depends on the settings in the configuration file.
*/
private final TimerStorageFactory timerStorageFactory = TimerStorageFactory.getFactory();
/**
* The StringConstraint to ensure a maximum length of strings.
*/
private final StringConstraint strConstraint;
/**
* The thread MX bean.
*/
private final ThreadMXBean threadMXBean;
/**
* Defines if the thread CPU time is supported.
*/
private boolean supported = false;
/**
* Defines if the thread CPU time is enabled.
*/
private boolean enabled = false;
/**
* The stack containing the start time values.
*/
private final ThreadLocalStack<Long> threadCpuTimeStack = new ThreadLocalStack<Long>();
/**
* The only constructor which needs the used {@link ICoreService} implementation and the used
* {@link Timer}.
*
* @param timer
* The timer.
* @param platformManager
* The Platform manager.
* @param propertyAccessor
* The property accessor.
* @param param
* Additional parameters passed to the {@link TimerStorageFactory} for proper
* initialization.
* @param threadMXBean
* The bean used to access the cpu time.
*/
public TimerHook(Timer timer, IPlatformManager platformManager, IPropertyAccessor propertyAccessor, Map<String, Object> param, ThreadMXBean threadMXBean) {
this.timer = timer;
this.platformManager = platformManager;
this.propertyAccessor = propertyAccessor;
this.threadMXBean = threadMXBean;
try {
// if it is even supported by this JVM
supported = threadMXBean.isThreadCpuTimeSupported();
if (supported) {
// check if its enabled
enabled = threadMXBean.isThreadCpuTimeEnabled();
if (!enabled) {
// try to enable it
threadMXBean.setThreadCpuTimeEnabled(true);
// check again now if it is enabled now
enabled = threadMXBean.isThreadCpuTimeEnabled();
}
}
} catch (RuntimeException e) {
// catching the runtime exceptions which could be thrown by the
// above statements.
LOG.warn("Exception in the TimerHook.", e);
}
timerStorageFactory.setParameters(param);
this.strConstraint = new StringConstraint(param);
}
/**
* {@inheritDoc}
*/
@Override
public void beforeBody(long methodId, long sensorTypeId, Object object, Object[] parameters, RegisteredSensorConfig rsc) {
timeStack.push(new Double(timer.getCurrentTime()));
if (enabled) {
threadCpuTimeStack.push(Long.valueOf(threadMXBean.getCurrentThreadCpuTime()));
}
}
/**
* {@inheritDoc}
*/
@Override
public void firstAfterBody(long methodId, long sensorTypeId, Object object, Object[] parameters, Object result, RegisteredSensorConfig rsc) {
timeStack.push(new Double(timer.getCurrentTime()));
if (enabled) {
threadCpuTimeStack.push(Long.valueOf(threadMXBean.getCurrentThreadCpuTime()));
}
}
/**
* {@inheritDoc}
*/
@Override
public void secondAfterBody(ICoreService coreService, long methodId, long sensorTypeId, Object object, Object[] parameters, Object result, RegisteredSensorConfig rsc) {
double endTime = timeStack.pop().doubleValue();
double startTime = timeStack.pop().doubleValue();
double duration = endTime - startTime;
// default setting to a negative number
double cpuDuration = -1.0d;
if (enabled) {
long cpuEndTime = threadCpuTimeStack.pop().longValue();
long cpuStartTime = threadCpuTimeStack.pop().longValue();
cpuDuration = (cpuEndTime - cpuStartTime) / 1000000.0d;
}
List<ParameterContentData> parameterContentData = null;
String prefix = null;
// check if some properties need to be accessed and saved
if (rsc.isPropertyAccess()) {
parameterContentData = propertyAccessor.getParameterContentData(rsc.getPropertyAccessorList(), object, parameters, result);
prefix = parameterContentData.toString();
// crop the content strings of all ParameterContentData but leave the prefix as it is
for (ParameterContentData contentData : parameterContentData) {
contentData.setContent(strConstraint.crop(contentData.getContent()));
}
}
ITimerStorage storage = (ITimerStorage) coreService.getObjectStorage(sensorTypeId, methodId, prefix);
if (null == storage) {
long platformId = platformManager.getPlatformId();
Timestamp timestamp = new Timestamp(System.currentTimeMillis() - Math.round(duration));
boolean charting = Boolean.TRUE.equals(rsc.getSettings().get("charting"));
storage = timerStorageFactory.newStorage(timestamp, platformId, sensorTypeId, methodId, parameterContentData, charting);
storage.addData(duration, cpuDuration);
coreService.addObjectStorage(sensorTypeId, methodId, prefix, storage);
} else {
storage.addData(duration, cpuDuration);
}
}
/**
* {@inheritDoc}
*/
@Override
public void beforeConstructor(long methodId, long sensorTypeId, Object[] parameters, RegisteredSensorConfig rsc) {
timeStack.push(new Double(timer.getCurrentTime()));
if (enabled) {
threadCpuTimeStack.push(Long.valueOf(threadMXBean.getCurrentThreadCpuTime()));
}
}
/**
* {@inheritDoc}
*/
@Override
public void afterConstructor(ICoreService coreService, long methodId, long sensorTypeId, Object object, Object[] parameters, RegisteredSensorConfig rsc) {
timeStack.push(new Double(timer.getCurrentTime()));
if (enabled) {
threadCpuTimeStack.push(Long.valueOf(threadMXBean.getCurrentThreadCpuTime()));
}
// just call the second after body method directly
secondAfterBody(coreService, methodId, sensorTypeId, object, parameters, null, rsc);
}
}