/*
* Copyright (c) 2010-2012 Grid Dynamics Consulting Services, Inc, All Rights Reserved
* http://www.griddynamics.com
*
* This library is free software; you can redistribute it and/or modify it under the terms of
* the Apache License; either
* version 2.0 of the License, or any later version.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.griddynamics.jagger.engine.e1.aggregator.workload;
import com.griddynamics.jagger.coordinator.NodeId;
import com.griddynamics.jagger.dbapi.entity.TaskData;
import com.griddynamics.jagger.dbapi.entity.MetricDescriptionEntity;
import com.griddynamics.jagger.dbapi.entity.MetricPointEntity;
import com.griddynamics.jagger.engine.e1.collector.*;
import com.griddynamics.jagger.engine.e1.scenario.WorkloadTask;
import com.griddynamics.jagger.master.CompositeTask;
import com.griddynamics.jagger.master.DistributionListener;
import com.griddynamics.jagger.master.Master;
import com.griddynamics.jagger.master.SessionIdProvider;
import com.griddynamics.jagger.master.configuration.Task;
import com.griddynamics.jagger.reporting.interval.CalculatedIntervalSizeProvider;
import com.griddynamics.jagger.reporting.interval.IntervalSizeProvider;
import com.griddynamics.jagger.storage.FileStorage;
import com.griddynamics.jagger.storage.KeyValueStorage;
import com.griddynamics.jagger.storage.Namespace;
import com.griddynamics.jagger.storage.fs.logging.*;
import com.griddynamics.jagger.util.TimeUnits;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.orm.hibernate3.HibernateCallback;
import java.io.File;
import java.io.IOException;
import java.net.URLDecoder;
import java.sql.SQLException;
import java.util.*;
/**
* Created with IntelliJ IDEA.
* User: nmusienko
* Date: 18.03.13
* Time: 16:26
* To change this template use File | Settings | File Templates.
*/
public class MetricLogProcessor extends LogProcessor implements DistributionListener {
private static final Logger log = LoggerFactory.getLogger(Master.class);
private LogAggregator logAggregator;
private LogReader logReader;
private SessionIdProvider sessionIdProvider;
private IntervalSizeProvider intervalSizeProvider;
private FileStorage fileStorage;
private MetricDescription defaultMetricDescription;
{
defaultMetricDescription = new MetricDescription("No name metric");
defaultMetricDescription.setPlotData(false);
defaultMetricDescription.setShowSummary(true);
defaultMetricDescription.setAggregators(Arrays.asList(new SumMetricAggregatorProvider()));
}
private KeyValueStorage keyValueStorage;
public void setKeyValueStorage(KeyValueStorage keyValueStorage) {
this.keyValueStorage = keyValueStorage;
}
@Required
public void setLogReader(LogReader logReader) {
this.logReader = logReader;
}
@Required
public void setFileStorage(FileStorage fileStorage) {
this.fileStorage = fileStorage;
}
public void setIntervalSizeProvider(IntervalSizeProvider intervalSizeProvider) {
this.intervalSizeProvider = intervalSizeProvider;
}
@Required
public void setSessionIdProvider(SessionIdProvider sessionIdProvider) {
this.sessionIdProvider = sessionIdProvider;
}
@Required
public void setLogAggregator(LogAggregator logAggregator) {
this.logAggregator = logAggregator;
}
@Override
public void onDistributionStarted(String sessionId, String taskId, Task task, Collection<NodeId> capableNodes) {
// do nothing
}
@Override
public void onTaskDistributionCompleted(String sessionId, String taskId, Task task) {
if (task instanceof WorkloadTask || task instanceof CompositeTask) {
processLog(sessionIdProvider.getSessionId(), taskId);
}
}
private void processLog(String sessionId, String taskId) {
try {
TaskData taskData = getTaskData(taskId, sessionId);
if (taskData == null) {
log.error("TaskData not found by taskId: {}", taskId);
return;
}
String dir = sessionId + File.separatorChar + taskId + File.separatorChar + MetricCollector.METRIC_MARKER + File.separatorChar;
Set<String> metrics = fileStorage.getFileNameList(dir);
for (String metricPath : metrics) {
try {
String file = metricPath + File.separatorChar + "aggregated.dat";
AggregationInfo aggregationInfo = logAggregator.chronology(metricPath, file);
if (aggregationInfo.getCount() == 0) {
//metric not collected
return;
}
StatisticsGenerator statisticsGenerator = new StatisticsGenerator(file, aggregationInfo, intervalSizeProvider, taskData)
.generate();
final Collection<MetricPointEntity> statistics = statisticsGenerator.getStatistics();
log.debug("BEGIN: Save to data base " + metricPath);
getHibernateTemplate().execute(new HibernateCallback<Void>() {
@Override
public Void doInHibernate(Session session) throws HibernateException, SQLException {
for (MetricPointEntity stat : statistics) {
session.persist(stat);
}
session.flush();
return null;
}
});
log.debug("END: Save to data base " + metricPath);
} catch (Exception e) {
log.error("Error during processing metric by path: '{}'", metricPath);
}
}
} catch (Exception e) {
log.error("Error during log processing", e);
}
}
private class StatisticsGenerator {
private String path;
private AggregationInfo aggregationInfo;
private IntervalSizeProvider intervalSizeProvider;
private TaskData taskData;
private Collection<MetricPointEntity> statistics;
StatisticsGenerator(String path, AggregationInfo aggregationInfo, IntervalSizeProvider intervalSizeProvider, TaskData taskData) {
this.path = path;
this.aggregationInfo = aggregationInfo;
this.intervalSizeProvider = intervalSizeProvider;
this.taskData = taskData;
this.statistics = new ArrayList<>();
}
public Collection<MetricPointEntity> getStatistics() {
return statistics;
}
public StatisticsGenerator generate() throws IOException {
String tmp = path.substring(0, path.lastIndexOf(File.separatorChar));
String metricName = tmp.substring(tmp.lastIndexOf(File.separatorChar) + 1);
metricName = URLDecoder.decode(metricName, "UTF-8");
MetricDescription metricDescription = fetchDescription(metricName);
if (metricDescription == null) {
log.warn("Aggregators not found for metric: '{}' in task: '{}'; Using default aggregator", metricName, taskData.getTaskId());
metricDescription = defaultMetricDescription;
metricDescription.setMetricId(metricName);
} else {
// if there are no aggregators - add default sum-aggregator
if (metricDescription.getAggregators().isEmpty()) {
log.warn("Aggregators not found for metric: '{}' in task: '{}'; Using default aggregator", metricName, taskData.getTaskId());
metricDescription.addAggregator(new SumMetricAggregatorProvider());
}
}
for (Map.Entry<MetricAggregatorProvider, MetricAggregatorSettings> entry : metricDescription.getAggregatorsWithSettings().entrySet()) {
collectStatistics(metricDescription, entry);
}
return this;
}
private void collectStatistics(MetricDescription metricDescription, Map.Entry<MetricAggregatorProvider, MetricAggregatorSettings> entry) {
MetricAggregator overallMetricAggregator = null;
MetricAggregator intervalAggregator = null;
MetricAggregatorSettings aggregatorSettings = entry.getValue();
TimeUnits normalizeByIntervalValue = aggregatorSettings.getNormalizationBy();
boolean normalizeRequired = (normalizeByIntervalValue != TimeUnits.NONE);
boolean normalizeFullIntervalRequired = aggregatorSettings.isNormalizeOnFullMeasuredInterval();
long normalizeByInterval = normalizeByIntervalValue.getMilliseconds();
long intervalSize = getIntervalSize(intervalSizeProvider, aggregatorSettings, aggregationInfo);
if (metricDescription.getShowSummary()) {
overallMetricAggregator = entry.getKey().provide();
}
if (metricDescription.getPlotData()) {
intervalAggregator = entry.getKey().provide();
}
if (metricDescription.getShowSummary() || metricDescription.getPlotData()) {
MetricAggregator nameAggregator = overallMetricAggregator == null ? intervalAggregator : overallMetricAggregator;
String aggregatorName = nameAggregator.getName();
String aggregatorIdSuffix = createIdFromName(aggregatorName, normalizeByIntervalValue);
String aggregatorDisplayNameSuffix = createAggregatorDisplayNameSuffix(aggregatorName, normalizeByIntervalValue);
String displayName = (metricDescription.getDisplayName() == null ? metricDescription.getMetricId() : metricDescription.getDisplayName()) + aggregatorDisplayNameSuffix;
String metricId = metricDescription.getMetricId() + '-' + aggregatorIdSuffix;
MetricDescriptionEntity metricDesc = persistMetricDescription(metricId, displayName, taskData);
long currentInterval = aggregationInfo.getMinTime() + intervalSize;
long time = intervalSize;
long extendedInterval = intervalSize;
int addedStatistics = 0;
try (LogReader.FileReader<MetricLogEntry> fileReader = logReader.read(path, MetricLogEntry.class)) {
for (MetricLogEntry logEntry : fileReader) {
log.debug("Log entry {} time", logEntry.getTime());
if (metricDescription.getPlotData()) {
while (logEntry.getTime() > currentInterval) {
Number aggregated = intervalAggregator.getAggregated();
if (aggregated != null) {
double value = aggregated.doubleValue();
// first point is removed because it's value very high due to the first invocation of invoker taking longer than the other
// and it breaks statistics JFG-729
if (++addedStatistics > 1)
addStatistics(intervalAggregator, normalizeRequired, normalizeFullIntervalRequired, normalizeByInterval, metricDesc, time, extendedInterval, value);
extendedInterval = intervalSize;
time += intervalSize;
currentInterval += intervalSize;
} else {
while (logEntry.getTime() > currentInterval) {
extendedInterval += intervalSize;
time += intervalSize;
currentInterval += intervalSize;
}
}
}
intervalAggregator.append(logEntry.getMetric());
}
if (metricDescription.getShowSummary())
overallMetricAggregator.append(logEntry.getMetric());
}
// first point is removed because it's value very high due to the first invocation of invoker taking longer than the other
// and it breaks statistics JFG-729
if (metricDescription.getPlotData() && ++addedStatistics > 1) {
Number aggregated = intervalAggregator.getAggregated();
if (aggregated != null) {
double value = aggregated.doubleValue();
addStatistics(intervalAggregator, normalizeRequired, normalizeFullIntervalRequired, normalizeByInterval, metricDesc, time, extendedInterval, value);
}
}
if (metricDescription.getShowSummary()) {
double value = overallMetricAggregator.getAggregated().doubleValue();
if (normalizeRequired) {
value = value * normalizeByInterval * 1d / time;
}
persistAggregatedMetricValue(value, metricDesc);
}
}
}
}
private void addStatistics(MetricAggregator intervalAggregator, boolean normalizeByTimeRequired, boolean normalizeByFullTimeIntervalRequired,
long normalizeByInterval, MetricDescriptionEntity metricDescriptionEntity, long time, long extendedInterval, double value) {
if (normalizeByTimeRequired) {
long intervalForNormalization;
if (!normalizeByFullTimeIntervalRequired) {
intervalForNormalization = extendedInterval;
} else {
intervalForNormalization = time;
}
value = value * normalizeByInterval * 1d / intervalForNormalization;
}
statistics.add(new MetricPointEntity(time - extendedInterval / 2, value, metricDescriptionEntity));
intervalAggregator.reset();
}
private int getIntervalSize(IntervalSizeProvider intervalSizeProvider,
MetricAggregatorSettings aggregatorSettings,
AggregationInfo aggregationInfo) {
long maxTime = aggregationInfo.getMaxTime();
long minTime = aggregationInfo.getMinTime();
int pointsCount = aggregatorSettings.getPointCount();
int aggregationInterval = aggregatorSettings.getPointInterval();
int intervalSize = intervalSizeProvider.getIntervalSize(minTime, maxTime);
if (pointsCount > 0) {
intervalSize = new CalculatedIntervalSizeProvider(pointsCount).getIntervalSize(minTime, maxTime);
}
if (intervalSize < 1)
intervalSize = 1;
if (aggregationInterval > 0) {
intervalSize = aggregationInterval;
}
return intervalSize;
}
private MetricDescription fetchDescription(String metricName) {
Collection<Object> metricDescription = keyValueStorage.fetchAll(
Namespace.of(taskData.getSessionId(), taskData.getTaskId(), "metricDescription"),
metricName
);
if (!metricDescription.iterator().hasNext()) {
return null;
}
return (MetricDescription) metricDescription.iterator().next();
}
/**
* Creates aggregator`s id from aggregator`s displayName.
* Replace all reserved symbols for aggregator`s name with empty String.
* Reserved symbols = ";" | "/" | "?" | ":" | "@" | "&" | "=" | "+" | "$" | ","
*
* @param name aggregator`s name
* @return aggregator`s id
*/
private String createIdFromName(String name, TimeUnits normalization) {
String result;
String regexp = "[\\;/\\?\\:@\\&=\\+\\$\\,]";
result = name.replaceAll(regexp, "");
switch (normalization) {
case NONE:
break;
case SECOND:
result += "_per_second";
break;
case MINUTE:
result += "_per_minute";
break;
case HOUR:
result += "_per_hour";
break;
}
return result;
}
/**
* Wrap aggregator name to make it more comfortable to read in pdf/webclient
*
* @param name aggregator`s name
* @return suffix for displayName of metric with given aggregator
*/
private String createAggregatorDisplayNameSuffix(String name, TimeUnits normalization) {
String result = " [" + name;
switch (normalization) {
case NONE:
break;
case SECOND:
result += "/second";
break;
case MINUTE:
result += "/minute";
break;
case HOUR:
result += "/hour";
break;
}
result += "]";
return result;
}
}
}