package org.epics.archiverappliance.mgmt.archivepv;
import java.io.IOException;
import java.sql.Timestamp;
import java.text.DecimalFormat;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.log4j.Logger;
import org.epics.archiverappliance.common.TimeUtils;
import org.epics.archiverappliance.config.ApplianceAggregateInfo;
import org.epics.archiverappliance.config.ApplianceInfo;
import org.epics.archiverappliance.config.ConfigService;
import org.epics.archiverappliance.utils.ui.GetUrlContent;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
/**
* Data from various appliances that is used for capacity planning.
* We assume that policies have been applied for this PV and that typeinfo is available.
* We gather data from various appliances (engine + ETL + storage) as it applies to this PV.
* Capacity planning consists of measured data and estimated data.
* The estimated data is available from the configService as ApplianceAggregateInfo
* The measured data is available thru this class.
* As the measured data does not change very often, we cache this locally for a time period as defined by MEASURED_DATA_CACHE_TIME
* @author mshankar
*
*/
public class CapacityPlanningData {
private static final int MEASURED_DATA_CACHE_TIME = 60*60*1000;
private static Logger logger = Logger.getLogger(CapacityPlanningData.class.getName());
/**
* This is the percentage of time taken (averaged over the lifetime of the engine) that the engine write thread takes to flush data into short term store.
*/
private float engineWriteThreadUsage;
private float secondsConsumedByWriter;
private float currentTotalStorageRate;
private float percentageTimeForWritterAfterPVadded=0;
/**
* ETL metrics for all the stores in this appliance that support the storage API.
*/
private ConcurrentHashMap<String, ETLMetrics> etlMetrics = new ConcurrentHashMap<String, ETLMetrics>();
private ApplianceAggregateInfo applianceAggregateInfoAsOfLastFetch;
private String identity;
private boolean isAvailible=true;
private static CPStaticData cachedCPStaticData = null;
class ETLMetrics {
String identity;
/**
* This is percentage time taken (averaged over the lifetime of ETL) that the ETL thread takes to move data from one store to another.
*/
double etlTimeTaken;
/**
this is the estimated storage size for pv just added.
estimateStoragePVadded=sum(pvDataRate*partitionTime);
*/
long estimateStoragePVadded=0;
double estimateETLtimePercentageAfterPVadded;
/**
* This is the raw storage (in bytes, averaged over the lifetime of ETL) available before ETL runs in all the various stores.
*/
long etlStorageAvailable;
/**
* This is the total size of the store.
*/
long totalSpace;
public ETLMetrics(JSONObject obj) {
this.identity = (String) obj.get("identity");
this.totalSpace = Long.parseLong((String) obj.get("totalSpace"));
this.etlStorageAvailable = Long.parseLong((String) obj.get("availableSpace"));
this.etlTimeTaken = Double.parseDouble((String) obj.get("avgTimeConsumedPercent"));
//avgTimeConsumedMs
}
}
public CapacityPlanningData(ConfigService configService, ApplianceInfo applianceInfo) throws IOException {
try{
identity = applianceInfo.getIdentity();
String engineURL = applianceInfo.getEngineURL() + "/getApplianceMetrics";
JSONObject engineMetrics = GetUrlContent.getURLContentAsJSONObject(engineURL);
DecimalFormat twoSignificantDigits = new DecimalFormat("###,###,###,###,###,###.##");
String secondsConsumedByWriterStr = (String) engineMetrics.get("secondsConsumedByWritter");
secondsConsumedByWriter=twoSignificantDigits.parse(secondsConsumedByWriterStr).floatValue();
String currentTotalStorageRateStr = (String) engineMetrics.get("dataRate");
if(currentTotalStorageRateStr != null) {
currentTotalStorageRate=twoSignificantDigits.parse(currentTotalStorageRateStr).floatValue();
}
String etlURL = applianceInfo.getEtlURL() + "/getStorageMetricsForAppliance";
JSONArray etlMetricsArray = GetUrlContent.getURLContentAsJSONArray(etlURL);
logger.debug(applianceInfo.getIdentity()+"'s ETLMetric:"+etlMetricsArray.toJSONString());
for(Object etlMetricObj : etlMetricsArray) {
ETLMetrics etlMetric = new ETLMetrics((JSONObject) etlMetricObj);
etlMetrics.put(etlMetric.identity, etlMetric);
}
applianceAggregateInfoAsOfLastFetch = configService.getAggregatedApplianceInfo(applianceInfo).clone();
} catch(Exception e) {
logger.error("Exception in CapacityPlanningMetricsPerApplianceForPV", e);
throw new IOException(e);
}
}
public static class CPStaticData {
public ConcurrentHashMap<ApplianceInfo, CapacityPlanningData> cpApplianceMetrics;
Timestamp timeofData;
public CPStaticData(ConcurrentHashMap<ApplianceInfo, CapacityPlanningData> cpApplianceMetrics, Timestamp timeofData) {
this.cpApplianceMetrics = cpApplianceMetrics;
this.timeofData = timeofData;
}
}
public static CPStaticData getMetricsForAppliances(ConfigService configService) throws IOException {
Timestamp now = TimeUtils.now();
if(cachedCPStaticData != null) {
if((now.getTime() - cachedCPStaticData.timeofData.getTime()) > MEASURED_DATA_CACHE_TIME) {
logger.debug("Refetching static data for capacity planning as it is stale " + (now.getTime() - cachedCPStaticData.timeofData.getTime()));
} else {
logger.debug("Using cached copy of measured data");
return cachedCPStaticData;
}
}
logger.debug("Fetching new capacity planning static data");
ConcurrentHashMap<ApplianceInfo, CapacityPlanningData> capacityMetrics = new ConcurrentHashMap<ApplianceInfo, CapacityPlanningData>();
for(ApplianceInfo applianceInfo : configService.getAppliancesInCluster()) {
capacityMetrics.put(applianceInfo, new CapacityPlanningData(configService, applianceInfo));
}
CPStaticData newStaticData = new CPStaticData(capacityMetrics, now);
cachedCPStaticData = newStaticData;
return cachedCPStaticData;
}
public float getEngineWriteThreadUsage(float writePeriod) {
engineWriteThreadUsage = (float) (secondsConsumedByWriter*100/writePeriod);
logger.debug("engineWriteThreadUsage for appliance " + identity + " is " + engineWriteThreadUsage);
return engineWriteThreadUsage;
}
public ConcurrentHashMap<String, ETLMetrics> getEtlMetrics() {
return etlMetrics;
}
public float getCurrentTotalStorageRate() {
return currentTotalStorageRate;
}
/**
* Return the difference between the appliance aggregate info as of "now" and from the time we last fetched the static data.
* @param configService ConfigService
* @return ApplianceAggregateInfo
* @throws IOException
*/
public ApplianceAggregateInfo getApplianceAggregateDifferenceFromLastFetch(ConfigService configService) throws IOException {
ApplianceAggregateInfo freshData = configService.getAggregatedApplianceInfo(configService.getAppliance(identity));
return freshData.getDifference(applianceAggregateInfoAsOfLastFetch);
}
public float getPercentageTimeForWritter() {
return percentageTimeForWritterAfterPVadded;
}
public void setPercentageTimeForWritter(float percentageTimeForWritterAfterPVadded) {
this.percentageTimeForWritterAfterPVadded = percentageTimeForWritterAfterPVadded;
}
public float getSecondsConsumedByWriter() {
return secondsConsumedByWriter;
}
public boolean isAvailible() {
return isAvailible;
}
public void setAvailible(boolean isAvailible) {
this.isAvailible = isAvailible;
}
public String getStaticDataLastUpdated() {
if(cachedCPStaticData != null) {
return TimeUtils.convertToHumanReadableString(cachedCPStaticData.timeofData);
} else {
return "Unknown";
}
}
}