package rocks.inspectit.server.storage;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import javax.annotation.Resource;
import org.apache.commons.collections.CollectionUtils;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import rocks.inspectit.server.dao.StorageDataDao;
import rocks.inspectit.shared.all.communication.DefaultData;
import rocks.inspectit.shared.all.communication.data.SystemInformationData;
import rocks.inspectit.shared.all.spring.logger.Log;
import rocks.inspectit.shared.cs.storage.StorageData;
import rocks.inspectit.shared.cs.storage.StorageWriter;
import rocks.inspectit.shared.cs.storage.processor.AbstractDataProcessor;
import rocks.inspectit.shared.cs.storage.recording.RecordingProperties;
import rocks.inspectit.shared.cs.storage.recording.RecordingState;
/**
* CMR storage recorder that uses the {@link StorageWriter} to provide recording functionality..
* Handles the scheduling, starting and stopping of the recording.
*
* @author Ivan Senic
*
*/
@Component
public class CmrStorageRecorder {
/**
* The log of this class.
*/
@Log
Logger log;
/**
* CMR storage manager.
*/
@Autowired
CmrStorageManager cmrStorageManager;
/**
* {@link StorageDataDao}.
*/
@Autowired
StorageDataDao storageDataDao;
/**
* {@link ExecutorService} for tasks of the tree handling.
*/
@Autowired
@Resource(name = "scheduledExecutorService")
ScheduledExecutorService executorService;
/**
* Future for the task of recording stop.
*/
private ScheduledFuture<?> stopRecordingFuture;
/**
* Future for the task of recording start.
*/
private ScheduledFuture<?> startRecordingFuture;
/**
* Set of involved Agents, used after recording to store proper Agent information.
*/
private Set<Long> involvedAgentsSet = new HashSet<>();
/**
* Storage writer to use for writing.
*/
private StorageWriter storageWriter;
/**
* Properties used when recording.
*/
private RecordingProperties recordingProperties;
/**
* Recording state. By default is not active.
*/
private volatile RecordingState recordingState = RecordingState.OFF;
/**
* Records this object, by processing it against the all the recording
* {@link AbstractDataProcessor}s that are defined in the {@link RecordingProperties} for this
* {@link StorageWriter}. Processor define which data will be stored, when and in which format.
* <p>
* If the processors are not set, then the normal write will be executed.
*
* @param defaultData
* Object to be processed.
*/
public void record(DefaultData defaultData) {
if (isRecordingOn() && storageWriter.isWritingOn()) {
Collection<AbstractDataProcessor> recordingDataProcessors = recordingProperties.getRecordingDataProcessors();
if (CollectionUtils.isNotEmpty(recordingDataProcessors)) {
for (AbstractDataProcessor dataProcessor : recordingDataProcessors) {
dataProcessor.process(defaultData);
}
}
involvedAgentsSet.add(defaultData.getPlatformIdent());
}
}
/**
* Starts or schedules the recording. If the recording properties define the start date that is
* after current time, recording will be scheduled. Otherwise it is started right away.
*
* @param stWriter
* Writer for executing writing tasks.
* @param recProperties
* {@link RecordingProperties} used during the recording.
* @throws IllegalArgumentException
* If parameters are null, storage writer is not prepared for write or data
* processor are not provided.
*/
public synchronized void startOrScheduleRecording(final StorageWriter stWriter, final RecordingProperties recProperties) throws IllegalArgumentException {
if (!isRecordingOn() && !isRecordingScheduled()) {
if (null == stWriter) {
throw new IllegalArgumentException("Storage writer can not be null. Recording will not be started.");
} else if (!stWriter.isWritingOn()) {
throw new IllegalArgumentException("Storage writer must be prepared for write. Recording will not be started.");
} else if (null == recProperties) {
throw new IllegalArgumentException("Recording properties can not be null. Recording will not be started.");
} else if (CollectionUtils.isEmpty(recProperties.getRecordingDataProcessors())) {
throw new IllegalArgumentException("Recording data processor must be provided for recording.");
}
storageWriter = stWriter;
recordingProperties = recProperties;
long startDelay = recProperties.getStartDelay();
if (startDelay > 0) {
recordingState = RecordingState.SCHEDULED;
Runnable startRecordingRunnable = new Runnable() {
@Override
public void run() {
CmrStorageRecorder.this.startRecording(storageWriter, recordingProperties);
}
};
startRecordingFuture = executorService.schedule(startRecordingRunnable, startDelay, TimeUnit.MILLISECONDS);
Date recordStartDate = new Date(System.currentTimeMillis() + startDelay);
recordingProperties.setRecordStartDate(recordStartDate);
} else {
startRecording(storageWriter, recordingProperties);
}
}
}
/**
* Prepares the writer for recording by passing the data processors that will be used when
* {@link #record(DefaultData)} is called.
*
* @param stWriter
* Writer for executing writing tasks.
* @param recProperties
* {@link RecordingProperties} used during the recording.
*/
private synchronized void startRecording(StorageWriter stWriter, RecordingProperties recProperties) {
if (!isRecordingOn()) {
storageWriter = stWriter;
recordingProperties = recProperties;
// prepare the processors if they are given
Collection<AbstractDataProcessor> recordingDataProcessors = recordingProperties.getRecordingDataProcessors();
if (null != recordingDataProcessors) {
for (AbstractDataProcessor abstractDataProcessor : recordingDataProcessors) {
abstractDataProcessor.setStorageWriter(storageWriter);
}
}
// update state
recordingState = RecordingState.ON;
// set start and end dates
recordingProperties.setRecordStartDate(new Date());
// set the task for stopping the recording if stop date is provided
long recordingDuration = recordingProperties.getRecordDuration();
if (recordingDuration > 0) {
Runnable stopRecordingRunnable = new Runnable() {
@Override
public void run() {
try {
cmrStorageManager.stopRecording();
} catch (Exception e) {
log.warn("Automatic stop of recording failed for the storage: " + getStorageData(), e);
}
}
};
stopRecordingFuture = executorService.schedule(stopRecordingRunnable, recordingDuration, TimeUnit.MILLISECONDS);
Date recordEndDate = new Date(System.currentTimeMillis() + recordingDuration);
recordingProperties.setRecordEndDate(recordEndDate);
}
if (log.isDebugEnabled()) {
log.info("Recording started for storage: " + getStorageData());
}
}
}
/**
* Stops recording by flushing all the recording processors.
*/
public synchronized void stopRecording() {
if (isRecordingOn()) {
if (null != stopRecordingFuture) {
if (!stopRecordingFuture.isDone() && !stopRecordingFuture.isCancelled()) {
stopRecordingFuture.cancel(false);
}
stopRecordingFuture = null; // NOPMD
}
Collection<AbstractDataProcessor> recordingDataProcessors = recordingProperties.getRecordingDataProcessors();
if (null != recordingDataProcessors) {
for (AbstractDataProcessor abstractDataProcessor : recordingDataProcessors) {
abstractDataProcessor.flush();
}
}
// save system info data if necessary
if (!involvedAgentsSet.isEmpty()) {
List<SystemInformationData> toRecord = storageDataDao.getSystemInformationData(involvedAgentsSet);
for (SystemInformationData defaultData : toRecord) {
record(defaultData);
}
}
involvedAgentsSet.clear();
if (log.isDebugEnabled()) {
log.info("Recording stopped for storage: " + getStorageData());
}
} else if (isRecordingScheduled()) {
if (null != startRecordingFuture) {
if (!startRecordingFuture.isDone() && !startRecordingFuture.isCancelled()) {
startRecordingFuture.cancel(false);
}
startRecordingFuture = null; // NOPMD
}
}
storageWriter = null; // NOPMD
recordingProperties = null; // NOPMD
recordingState = RecordingState.OFF;
}
/**
* Is recording active. The recording is active only when the {@link #storageWriter}
* {@link #recordingProperties} are set.
*
* @return True if the recording is active.
*/
public boolean isRecordingOn() {
return recordingState == RecordingState.ON;
}
/**
* Is recording scheduled.
*
* @return True if the recording is scheduled.
*/
public boolean isRecordingScheduled() {
return recordingState == RecordingState.SCHEDULED;
}
/**
* Returns the {@link StorageData} that is used for recording.
*
* @return Returns the {@link StorageData} that is used for recording.
*/
protected StorageData getStorageData() {
return storageWriter.getStorageData();
}
/**
* Gets {@link #recordingState}.
*
* @return {@link #recordingState}
*/
public RecordingState getRecordingState() {
return recordingState;
}
/**
* @return the storageWriter
*/
public StorageWriter getStorageWriter() {
return storageWriter;
}
/**
* @return the recordingProperties
*/
public RecordingProperties getRecordingProperties() {
return recordingProperties;
}
}