package org.epics.archiverappliance.etl.common; import java.io.IOException; import java.nio.file.FileStore; import java.nio.file.Files; import java.nio.file.Path; import java.util.HashMap; import org.apache.log4j.Logger; import org.epics.archiverappliance.common.TimeUtils; import org.epics.archiverappliance.etl.StorageMetricsContext; import org.epics.archiverappliance.utils.nio.ArchPaths; /** * ETL metrics for the appliance as a whole for one lifetime * @author mshankar * */ public class ETLMetricsForLifetime implements StorageMetricsContext { private static Logger logger = Logger.getLogger(ETLMetricsForLifetime.class.getName()); int lifeTimeId; long totalETLRuns; long timeForOverallETLInMilliSeconds; long startOfMetricsMeasurementInEpochSeconds; long timeinMillSecond4appendToETLAppendData; long timeinMillSecond4commitETLAppendData; long timeinMillSecond4executePostETLTasks; long timeinMillSecond4getETLStreams; long timeinMillSecond4checkSizes; long timeinMillSecond4markForDeletion; long timeinMillSecond4prepareForNewPartition; long timeinMillSecond4runPostProcessors; long totalSrcBytes; private long approximateLastGlobalETLTimeInMillis = 0; private long lastTimeGlobalETLTimeWasUpdatedInEpochSeconds = 0; private long[] weeklyETLUsageInMillis = new long[7]; private HashMap<String, FileStore> storageMetricsFileStores = new HashMap<String, FileStore>(); public ETLMetricsForLifetime(int lifeTimeId) { this.lifeTimeId = lifeTimeId; this.startOfMetricsMeasurementInEpochSeconds = TimeUtils.getCurrentEpochSeconds(); for(int i = 0; i < 7; i++) { weeklyETLUsageInMillis[i] = -1; } } public int getLifeTimeId() { return lifeTimeId; } public long getTotalETLRuns() { return totalETLRuns; } public long getTimeForOverallETLInMilliSeconds() { return timeForOverallETLInMilliSeconds; } public long getStartOfMetricsMeasurementInEpochSeconds() { return startOfMetricsMeasurementInEpochSeconds; } public long getTimeinMillSecond4appendToETLAppendData() { return timeinMillSecond4appendToETLAppendData; } public long getTimeinMillSecond4commitETLAppendData() { return timeinMillSecond4commitETLAppendData; } public long getTimeinMillSecond4executePostETLTasks() { return timeinMillSecond4executePostETLTasks; } public long getTimeinMillSecond4getETLStreams() { return timeinMillSecond4getETLStreams; } public long getTimeinMillSecond4markForDeletion() { return timeinMillSecond4markForDeletion; } public long getTimeinMillSecond4prepareForNewPartition() { return timeinMillSecond4prepareForNewPartition; } public long getTimeinMillSecond4runPostProcessors() { return timeinMillSecond4runPostProcessors; } public long getTimeinMillSecond4checkSizes() { return timeinMillSecond4checkSizes; } public long getTotalSrcBytes() { return totalSrcBytes; } /** * Update the time taken for the last ETL job. Note this is an approximation. * @param lastETLTimeWeSpentInETLInMilliSeconds   */ public void updateApproximateGlobalLastETLTime(long lastETLTimeWeSpentInETLInMilliSeconds) { try { // This is complex (and inaccurate) because the ETL jobs are done on a per PV basis // There is no concept of a global "job" that has a start time and an end time. // Instead, we use a concept of lastUpdate timestamp to make a call on whether to reset the last ETL metric or not. // Still unclear if this is actually useful.. long epochSeconds = System.currentTimeMillis()/1000; // We use some kind of cutoff as the boundary between this job and the previous. // What this means is that this number is more accurate if the time between one global ETL job and the next is at least this interval. // Otherwise this tends to accumulate times from multiple jobs. if((epochSeconds - lastTimeGlobalETLTimeWasUpdatedInEpochSeconds) > 15*60) { logger.debug("Resetting approximateLastGlobalETLTimeInMillis for updated for " + this.lifeTimeId); approximateLastGlobalETLTimeInMillis = 0; } lastTimeGlobalETLTimeWasUpdatedInEpochSeconds = epochSeconds; approximateLastGlobalETLTimeInMillis += lastETLTimeWeSpentInETLInMilliSeconds; // An alternate approach is to determine how much time we spend in ETL over a fixed last time period // We choose a week and an days's partition - so we have 7 buckets // One of the bucket's (tomorrow's) is almost always 0 // One of the bucket's (todays's) is almost always incomplete long epochDays = epochSeconds/(24*60*60); int dayBucket = (int) (epochDays % 7); weeklyETLUsageInMillis[dayBucket] = weeklyETLUsageInMillis[dayBucket] + lastETLTimeWeSpentInETLInMilliSeconds; // Reset tomorrow's times.. int nextDay = (dayBucket + 1) % 7; weeklyETLUsageInMillis[nextDay] = -1; } catch (Exception ex) { logger.error("Exception updating global ETL times", ex); } } /** * Note this is an approximation and could be inaccurate. * @return the approximateLastGlobalETLTime */ public long getApproximateLastGlobalETLTimeInMillis() { return approximateLastGlobalETLTimeInMillis; } /** * Get an estimate of how much time (in percent) over the last week we spent performing ETL for this transition. * @return time estimated over the last week */ public double getWeeklyETLUsageInPercent() { long epochSeconds = System.currentTimeMillis()/1000; long startOfEpochDayInSeconds = (epochSeconds/(24*60*60))*(24*60*60); long secondsIntoDay = epochSeconds - startOfEpochDayInSeconds; long totalWeeklyETLMillis = 0; long totalDaysInMetric = 0; for(long dailyUsageInMillis : weeklyETLUsageInMillis) { if(dailyUsageInMillis != -1) { totalWeeklyETLMillis += dailyUsageInMillis; totalDaysInMetric++; } } long totalWeeklyETLSeconds = totalWeeklyETLMillis/1000; // See updateApproximateGlobalLastETLTime // One of the bucket's (todays's) is almost always incomplete totalDaysInMetric = totalDaysInMetric - 1; long totalSecondsInMetric = totalDaysInMetric * (24*60*60) + secondsIntoDay; logger.debug("totalWeeklyETLSeconds = " + totalWeeklyETLSeconds + " totalSecondsInMetric " + totalSecondsInMetric); return (totalWeeklyETLSeconds*100.0)/totalSecondsInMetric; } /* (non-Javadoc) * @see org.epics.archiverappliance.etl.StorageMetricsContext#getFileStore(java.lang.String) */ @Override public FileStore getFileStore(String rootFolder) throws IOException { FileStore fileStore = this.storageMetricsFileStores.get(rootFolder); if(fileStore == null) { try(ArchPaths paths = new ArchPaths()) { Path rootF = paths.get(rootFolder); fileStore = Files.getFileStore(rootF); this.storageMetricsFileStores.put(rootFolder, fileStore); logger.debug("Adding filestore to ETLMetricsForLifetime cache for rootFolder " + rootFolder); } } else { logger.debug("Filestore for rootFolder " + rootFolder + " is already in ETLMetricsForLifetime cache"); } return fileStore; } }