All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.latency4j.persistence.DefaultWorkDurationPersistenceManager Maven / Gradle / Ivy

The newest version!
package org.latency4j.persistence;

import static org.latency4j.Latency4JConstants.JAVA_TMP_FILE_FOLDER;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import org.latency4j.Latency4JConstants;
import org.latency4j.Latency4JException;
import org.latency4j.LatencyRequirement;
import org.latency4j.WorkDuration;
import org.latency4j.WorkDurationId;
import org.latency4j.configuration.LatencyRequirementConfiguration;
import org.latency4j.util.FileUtilities;
import org.latency4j.util.LockUtil;
import org.latency4j.util.PrimitiveTypeUtilities;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 

* Default {@link WorkDurationPersistenceManager persistence manager} shipped * with the Latency4J toolkit. This implementation is file-based in the sense that * it reads and writes {@link WorkDuration durations} to a specified location on * the filesystem. *

*/ public class DefaultWorkDurationPersistenceManager implements WorkDurationPersistenceManager { private static final Logger logger = LoggerFactory.getLogger(DefaultWorkDurationPersistenceManager.class); /** *

* Key ({@value #DATA_DIRECTORY_CONFIG_PARAM}) to the * {@link LatencyRequirementConfiguration#getPersistenceManagerParameters() * configuration parameter} that specifies the directory to which * {@link WorkDuration duration} files should be persisted. Note that if the * specified directory does not currently exist, this manager will attempt * to create it. *

*

* Where not specified, this parameter defaults to the system temp folder as * identified from the java runtime property * {@link Latency4JConstants#JAVA_TMP_FILE_FOLDER}. *

*/ public static final String DATA_DIRECTORY_CONFIG_PARAM = "data.directory"; /** *

* Key ({@value #MAX_HISTORICAL_DATA_FILESIZE_CONFIG_PARAM}) to the * {@link LatencyRequirementConfiguration#getPersistenceManagerParameters() * configuration parameter} which specifies the maximum size of * {@link WorkDuration duration} files. *

*

* In order to prevent stale statistics on system startup, it is advisable * to delete the duration files which have exceeded, reached or nearing this * size so that the toolkit can replace them with more up-to-date records. *

*

* The default value of this parameter is * {@link #DEFAULT_MAX_FILESIZE}. *

*/ public static final String MAX_HISTORICAL_DATA_FILESIZE_CONFIG_PARAM = "max.file.bytes"; /** *

* The default value of the configuration parameter ( * {@value #MAX_HISTORICAL_DATA_FILESIZE_CONFIG_PARAM}kb). *

*/ public static final long DEFAULT_MAX_FILESIZE = new Long(4194304); // 4mb /** *

* The map of configuration parameters with which the instance is * initialised. *

*/ private Map parameters; private long maxFileSize; /** *

* A map of file {@link DurationFileHandle output-stream handles}, to which * {@link WorkDuration duration} instances are persisted. There is a 1:1 * mapping between the {@link LatencyRequirement requirements} to which the * {@link WorkDurationPersistenceManager persistence manager} relates, and * the {@link DurationFileHandle output-stream handles}. Put differently, * there should be an {@link DurationFileHandle output-stream handle} for * each {@link LatencyRequirement requirement} to which the * {@link WorkDurationPersistenceManager persistence-manager} relates. *

*/ private volatile Map outputFileHandles; private File outputDirectory; /** *

* Lock which is used to control insertion into the * {@link #outputFileHandles output-stream handles} map. *

*/ private final Lock outputStreamsLock; /** *

* An internal field which specifies whether or not the instance has been * initialised. Requests to this instance will fail if the value of this * field is false. *

*/ private final AtomicBoolean initialized; /** *

* Default constructor. *

*/ public DefaultWorkDurationPersistenceManager() { initialized = new AtomicBoolean(false); maxFileSize = DEFAULT_MAX_FILESIZE; outputFileHandles = new HashMap(); outputStreamsLock = new ReentrantLock(); } @Override public synchronized void init() { if (this.parameters == null) this.parameters = new HashMap(); String outputFolderName = System.getProperty(JAVA_TMP_FILE_FOLDER); if (parameters != null && parameters.containsKey(DATA_DIRECTORY_CONFIG_PARAM)) outputFolderName = parameters.get(DATA_DIRECTORY_CONFIG_PARAM); if (parameters != null && parameters.containsKey(MAX_HISTORICAL_DATA_FILESIZE_CONFIG_PARAM)) { String maxFileSizeStr = parameters.get(MAX_HISTORICAL_DATA_FILESIZE_CONFIG_PARAM); if (!PrimitiveTypeUtilities.isValidLong(maxFileSizeStr)) logger.warn("PersistenceManager parameter '{}', is not a valid long! Saw value '{}'", MAX_HISTORICAL_DATA_FILESIZE_CONFIG_PARAM, maxFileSizeStr); else maxFileSize = PrimitiveTypeUtilities.toLongValue(maxFileSizeStr); } outputDirectory = prepareOutputDirectoryForWritting(outputFolderName); initialized.set(true); } @Override public void setParameters(final Map parameters) { this.parameters = parameters; } @Override public Map getParameters() { return parameters; } @Override public synchronized void save(final WorkDuration taskDuration) { assertInitialized(); persistDuration(taskDuration); } @Override public synchronized List loadHistoricalData(final String workCategory) { assertInitialized(); List result = new ArrayList(); File dataFileHandle; try { dataFileHandle = new File(this.outputDirectory, DurationFileHandle.getDataFileName(workCategory)); List linesFromFile = FileUtilities.readLinesFromFile(dataFileHandle, DurationFileHandle.DATA_FILE_LINE_DELIMITER); WorkDuration duration; int lineNumber = 0; for (String durationLine : linesFromFile) { duration = parseDurationFromLine(durationLine, dataFileHandle, lineNumber); lineNumber++; result.add(duration); } } catch (Throwable error) { logger.warn("No historical data file for latency-requirement: " + workCategory + ". " + error.getMessage()); } return result; } public File getOutputDirectory() { return outputDirectory; } /** *

* Saves the specified {@link WorkDuration duration} to a data-file * corresponding to the {@link WorkDurationId#getWorkCategory() category} to * which it relates and as determined by the method * {@link #getDataFileName(String)}. The * {@link WorkDurationId#getWorkCategory() category} is obtained from the * {@link WorkDuration#getIdentifier() identifier} of the specified * {@link WorkDuration duration}. *

* * @param duration * The data to save. */ private void persistDuration(final WorkDuration duration) { try { DurationFileHandle dataFileHandle = getFileHandle(duration.getIdentifier().getWorkCategory()); dataFileHandle.saveDuration(duration); } catch (Throwable error) { logger.warn("Error persisting task-duration '" + duration.getIdentifier() + "': " + error.getMessage()); } } private DurationFileHandle getFileHandle(final String category) throws IOException { DurationFileHandle result = outputFileHandles.get(category); if (result == null) { try { LockUtil.acquireLock(this.outputStreamsLock); result = this.outputFileHandles.get(category); if (result == null) { result = new DurationFileHandle(outputDirectory, category, maxFileSize); outputFileHandles.put(category, result); } } finally { LockUtil.releaseLock(outputStreamsLock); } } return result; } /** *

* Parses a {@link WorkDuration duration} record from the specified line * taken from an input data-file. This method utilises the delegate method * {@link #createDuration(String, String, String, long, long, boolean, boolean)} * to create the object representation of the record. *

* * @param line * The line from which the data is to be parsed. * @param dataFile * The file from which the record was read. This is specified for * error reporting only. * @param lineNumber * The line-number in the input file from which the record was * read. This parameter is also specified for error reporting * purposes only. * * @return An object/parsed representation of the input line. */ private WorkDuration parseDurationFromLine(final String line, final File dataFile, final int lineNumber) { WorkDuration result = null; StringTokenizer tokenizer = new StringTokenizer(line, DurationFileHandle.DATA_DELIMETER, false); String workCategory = null, threadId = null, methodName = null, startTimeText = null, endTimeText = null, rootText = null, erroredText = null; long start, end; boolean root, errored; if (tokenizer.hasMoreElements()) workCategory = tokenizer.nextToken(); else { logger.warn("Error reading data file from directory '" + dataFile.getAbsolutePath() + "'. Missing category information. Line# " + lineNumber); return null; } if (tokenizer.hasMoreElements()) threadId = tokenizer.nextToken(); else { logger.warn("Error reading data file from directory '" + dataFile.getAbsolutePath() + "'. Missing threadId . Line# " + lineNumber); return null; } if (tokenizer.hasMoreElements()) methodName = tokenizer.nextToken(); else { logger.warn("Error reading data file from directory '" + dataFile.getAbsolutePath() + "'. Missing method-name. Line# " + lineNumber); return null; } if (tokenizer.hasMoreElements()) startTimeText = tokenizer.nextToken(); else throw new Latency4JException("Error reading data file from directory '" + dataFile.getAbsolutePath() + "'. Missing start-time. Line# " + lineNumber); if (tokenizer.hasMoreElements()) endTimeText = tokenizer.nextToken(); else throw new Latency4JException("Error reading data file from directory '" + dataFile.getAbsolutePath() + "'. Missing end-time. Line# " + lineNumber); if (tokenizer.hasMoreElements()) rootText = tokenizer.nextToken(); else throw new Latency4JException("Error reading data file from directory '" + dataFile.getAbsolutePath() + "'. Missing thread-stack root marker. Line# " + lineNumber); if (tokenizer.hasMoreElements()) erroredText = tokenizer.nextToken(); else throw new Latency4JException("Error reading data file from directory '" + dataFile.getAbsolutePath() + "'. Missing termination-code. Line# " + lineNumber); if (!PrimitiveTypeUtilities.isValidLong(startTimeText) || !PrimitiveTypeUtilities.isValidLong(endTimeText)) throw new Latency4JException("Failure to read line due to invalid start/end time formats. " + "Error reading historical data from: " + dataFile.getAbsolutePath() + ". Line#" + lineNumber); if (!PrimitiveTypeUtilities.isValidBoolean(rootText) || !PrimitiveTypeUtilities.isValidBoolean(erroredText)) throw new Latency4JException("Failure to read line due to invalid boolean fields. " + "Error reading historical data from: " + dataFile.getAbsolutePath() + ". Line#" + lineNumber); { start = PrimitiveTypeUtilities.toLongValue(startTimeText); end = PrimitiveTypeUtilities.toLongValue(endTimeText); root = Boolean.parseBoolean(rootText); errored = Boolean.parseBoolean(erroredText); result = createDuration(workCategory, threadId, methodName, start, end, root, errored); } return result; } /** *

* Delegate method which encapsulates the logic for assembling a * {@link WorkDuration duration} instance from its primitive constituents. * This method acts as a utility delegated for the method * {@link #parseDurationFromLine(String, File, int)} *

* * @param workContext * The {@link WorkDurationId#getWorkCategory() context or * category} to which the duration belongs. * * @param threadId * The {@link WorkDurationId#getThreadId() id of the thread} in * which the {@link WorkDuration duration} was generated. * * @param methodName * The {@link WorkDuration#getMethodName() name of the method} to * which the duration relates. * * @param start * The {@link WorkDuration#getStartTime() start time} of the task * segment to which this record relates. * * @param end * The {@link WorkDuration#getEndTime() end time} of the task to * which this record relates. * * @param root * Indicates if the record is for the * {@link WorkDuration#isRoot() root} of the task call stack. * * @param errored * Indicates if the record relates to a * {@link WorkDuration#isErrored() failed} task. * * @return Returns the {@link WorkDuration duration} instance created from * the specified field values. */ private WorkDuration createDuration(final String workContext, final String threadId, final String methodName, final long start, final long end, final boolean root, final boolean errored) { WorkDurationId identifier = new WorkDurationId(workContext, threadId); return new WorkDuration(identifier, methodName, start, end, root, errored); } /** *

* Ensures that the directory to which this manager writes has been created * and is writable. Note that where the directory does not already exist, * this method will attempt to create it. *

* * @param dataDirectory * The directory to which this manager has been initialised. * @return The directory to which this manager should write its output. */ private File prepareOutputDirectoryForWritting(final String dataDirectory) { File resullt = new File(dataDirectory); if (!resullt.exists() && !resullt.isDirectory()) { if (!resullt.mkdirs()) throw new Latency4JException("Unable to open data output directory:" + dataDirectory); } return resullt; } /** * Internal assertion which verifies that this manager has been initialised * and is ready for use. * * @throws Latency4JException * If the manager has not been initialised. */ private void assertInitialized() { if (!initialized.get()) throw new Latency4JException("Latency4J PersistenceManager not initialised."); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy