package rocks.inspectit.server.service; import java.lang.ref.SoftReference; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.TimeUnit; import javax.annotation.PostConstruct; import org.slf4j.Logger; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import rocks.inspectit.server.dao.DefaultDataDao; import rocks.inspectit.server.spring.aop.MethodLog; import rocks.inspectit.server.util.AgentStatusDataProvider; import rocks.inspectit.server.util.Converter; import rocks.inspectit.shared.all.cmr.property.spring.PropertyUpdate; import rocks.inspectit.shared.all.cmr.service.IAgentStorageService; import rocks.inspectit.shared.all.communication.DefaultData; import rocks.inspectit.shared.all.spring.logger.Log; import rocks.inspectit.shared.cs.cmr.service.ICmrManagementService; /** * The default implementation of the {@link IAgentStorageService} interface. Uses an implementation * of the {@link DefaultDataDao} interface to save and retrieve the data objects from the database. * * @author Patrice Bouillet * */ @Service public class AgentStorageService implements IAgentStorageService { /** The logger of this class. */ @Log Logger log; /** * Queue capacity for incoming data. */ private static final int QUEUE_CAPACITY = 50; /** * Amount of milliseconds after which the data is thrown away if queue is full. */ private static final long DATA_THROW_TIMEOUT_MILLIS = 10; /** * The default data DAO. */ @Autowired private DefaultDataDao defaultDataDao; /** * {@link AgentStatusDataProvider}. */ @Autowired AgentStatusDataProvider platformIdentDateSaver; /** * {@link CmrManagementService}. */ @Autowired ICmrManagementService cmrManagementService; /** * Queue to store and remove list of data that has to be processed. */ private ArrayBlockingQueue<SoftReference<List<? extends DefaultData>>> dataObjectsBlockingQueue = new ArrayBlockingQueue<>(QUEUE_CAPACITY); /** * Count of thread to process data. */ @Value("${cmr.agentStorageServiceThreadCount}") private int threadCount; /** * List of currently active threads that process the data. */ private List<Thread> threadList = new ArrayList<>(); /** * Default constructor. */ public AgentStorageService() { } /** * Constructor that can be used in testing for suppling the queue. * * @param dataObjectsBlockingQueue * Queue. */ AgentStorageService(ArrayBlockingQueue<SoftReference<List<? extends DefaultData>>> dataObjectsBlockingQueue) { this.dataObjectsBlockingQueue = dataObjectsBlockingQueue; } /** * {@inheritDoc} */ @Override @MethodLog public void addDataObjects(final List<? extends DefaultData> dataObjects) { SoftReference<List<? extends DefaultData>> softReference = new SoftReference<List<? extends DefaultData>>(dataObjects); if (!dataObjects.isEmpty()) { platformIdentDateSaver.registerDataSent(dataObjects.get(0).getPlatformIdent()); } try { boolean added = dataObjectsBlockingQueue.offer(softReference, DATA_THROW_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); if (!added) { int droppedSize = dataObjects.size(); if (log.isTraceEnabled()) { log.trace("Data dropped on the CMR due to the high volume of incoming data from Agent(s). Dropped data objects count: " + droppedSize); } cmrManagementService.addDroppedDataCount(droppedSize); } } catch (InterruptedException e) { return; } } /** * Updates the number of data processing threads. The new number of threads should be defined in * {@link #threadCount} before calling this method. * <p> * This is an automated properties update execution method. */ @PropertyUpdate(properties = { "cmr.agentStorageServiceThreadCount" }) public synchronized void updateThreadCount() { if (threadCount <= 0) { threadCount = 1; } int threadListSize = threadList.size(); if (threadCount < threadListSize) { // remove threads for (int i = 0; i < (threadListSize - threadCount); i++) { Thread thread = threadList.remove(i); thread.interrupt(); try { thread.join(); } catch (InterruptedException e) { Thread.interrupted(); } } } else if (threadCount > threadListSize) { // add new threads for (int i = threadListSize; i < threadCount; i++) { ProcessDataThread processDataThread = new ProcessDataThread(i); processDataThread.start(); threadList.add(processDataThread); } } } /** * Is executed after dependency injection is done to perform any initialization. * * @throws Exception * if an error occurs during {@link PostConstruct} */ @PostConstruct public void postConstruct() throws Exception { updateThreadCount(); if (log.isInfoEnabled()) { log.info("|-Agent Storage Service active..."); } } /** * Thread class that is processing the data coming to the Agent service and invoking the * {@link DefaultDataDao}. * * @author Ivan Senic * */ private class ProcessDataThread extends Thread { /** * Default constructor. * * @param threadId * Id of the thread that will be added to the thread name. */ public ProcessDataThread(int threadId) { setName("agent-storage-service-process-data-thread-" + threadId); setDaemon(true); } /** * {@inheritDoc} */ @Override public void run() { while (true) { if (isInterrupted()) { break; } SoftReference<List<? extends DefaultData>> softReference = null; try { softReference = dataObjectsBlockingQueue.take(); } catch (InterruptedException e) { this.interrupt(); return; } List<? extends DefaultData> defaultDataList = softReference.get(); if (defaultDataList != null) { long time = 0; if (log.isDebugEnabled()) { time = System.nanoTime(); } defaultDataDao.saveAll(defaultDataList); if (log.isDebugEnabled()) { log.debug("Data Objects count: " + defaultDataList.size() + " Save duration: " + Converter.nanoToMilliseconds(System.nanoTime() - time)); } } } } } }