package com.griddynamics.jagger.master;
import com.griddynamics.jagger.coordinator.NodeContext;
import com.griddynamics.jagger.coordinator.NodeId;
import com.griddynamics.jagger.dbapi.entity.DecisionPerMetricEntity;
import com.griddynamics.jagger.dbapi.entity.DecisionPerTaskEntity;
import com.griddynamics.jagger.dbapi.entity.MetricDescriptionEntity;
import com.griddynamics.jagger.dbapi.entity.TaskData;
import com.griddynamics.jagger.dbapi.util.SessionMatchingSetup;
import com.griddynamics.jagger.engine.e1.BasicTGDecisionMakerListener;
import com.griddynamics.jagger.engine.e1.Provider;
import com.griddynamics.jagger.engine.e1.ProviderUtil;
import com.griddynamics.jagger.engine.e1.collector.limits.*;
import com.griddynamics.jagger.engine.e1.collector.testgroup.TestGroupDecisionMakerInfo;
import com.griddynamics.jagger.engine.e1.scenario.WorkloadTask;
import com.griddynamics.jagger.engine.e1.services.DefaultDataService;
import com.griddynamics.jagger.engine.e1.services.JaggerPlace;
import com.griddynamics.jagger.engine.e1.services.data.service.MetricEntity;
import com.griddynamics.jagger.engine.e1.services.data.service.MetricSummaryValueEntity;
import com.griddynamics.jagger.engine.e1.services.data.service.TestEntity;
import com.griddynamics.jagger.util.Decision;
import com.griddynamics.jagger.engine.e1.collector.testgroup.TestGroupDecisionMakerListener;
import com.griddynamics.jagger.engine.e1.sessioncomparation.WorstCaseDecisionMaker;
import com.griddynamics.jagger.master.configuration.Task;
import org.hibernate.*;
import org.hibernate.Session;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import java.sql.SQLException;
import java.util.*;
public class DecisionMakerDistributionListener extends HibernateDaoSupport implements DistributionListener {
private static final Logger log = LoggerFactory.getLogger(DecisionMakerDistributionListener.class);
private NodeContext nodeContext;
private WorstCaseDecisionMaker worstCaseDecisionMaker = new WorstCaseDecisionMaker();
public DecisionMakerDistributionListener() {}
public void setNodeContext(NodeContext nodeContext) {
this.nodeContext = nodeContext;
}
@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 CompositeTask) {
DefaultDataService dataService = new DefaultDataService(nodeContext);
// Get tests in test group
CompositeTask compositeTask = (CompositeTask) task;
List<WorkloadTask> workloadTasks = new ArrayList<WorkloadTask>();
for (CompositableTask compositableTask : compositeTask.getAttendant()) {
if (compositableTask instanceof WorkloadTask) {
workloadTasks.add((WorkloadTask) compositableTask);
}
}
for (CompositableTask compositableTask : compositeTask.getLeading()) {
if (compositableTask instanceof WorkloadTask) {
workloadTasks.add((WorkloadTask) compositableTask);
}
}
// Make decision per tests
Set<DecisionPerTest> decisionsPerTest = new HashSet<DecisionPerTest>();
for (WorkloadTask workloadTask : workloadTasks) {
if (workloadTask.getLimits() != null) {
String testName = workloadTask.getName();
// Get data for current session
TestEntity testEntity = dataService.getTestByName(sessionId,testName);
Set<MetricEntity> metricEntitySet = dataService.getMetrics(testEntity);
Map<MetricEntity,MetricSummaryValueEntity> metricValues = dataService.getMetricSummary(metricEntitySet);
Map<String,MetricEntity> idToEntity = new HashMap<String, MetricEntity>();
for (MetricEntity metricEntity : metricValues.keySet()) {
idToEntity.put(metricEntity.getMetricId(),metricEntity);
}
// Get relation limit <-> metrics
boolean needBaselineSessionValue = false;
Map<Limit,Set<MetricEntity>> limitToEntity = new HashMap<Limit, Set<MetricEntity>>();
for (Limit limit : workloadTask.getLimits().getLimits()) {
limitToEntity.put(limit,getMetricsForLimit(limit, idToEntity));
if (!limitToEntity.get(limit).isEmpty()) {
if (limit.getRefValue() == null) {
needBaselineSessionValue = true;
}
}
}
// Get data for baseline session
Map<String,Double> metricIdToValuesBaseline = new HashMap<String, Double>();
if (needBaselineSessionValue) {
String baselineId = workloadTask.getLimits().getBaselineId(sessionId);
TestEntity testEntityBaseline = null;
// Strategy to match sessions - we will use baseline only when all test parameters are matching
SessionMatchingSetup sessionMatchingSetup = new SessionMatchingSetup(true,
EnumSet.of(SessionMatchingSetup.MatchBy.ALL));
Map<String, Set<TestEntity>> matchingTestEntities =
dataService.getTestsWithName(Arrays.asList(sessionId,baselineId), testName, sessionMatchingSetup);
if (!matchingTestEntities.get(baselineId).isEmpty()) {
testEntityBaseline = matchingTestEntities.get(baselineId).iterator().next();
}
if (testEntityBaseline != null) {
Set<MetricEntity> metricEntitySetBaseline = dataService.getMetrics(testEntityBaseline);
Map<MetricEntity,MetricSummaryValueEntity> metricValuesBaseline = dataService.getMetricSummary(metricEntitySetBaseline);
for (Map.Entry<MetricEntity,MetricSummaryValueEntity> entry : metricValuesBaseline.entrySet()) {
metricIdToValuesBaseline.put(entry.getKey().getMetricId(),entry.getValue().getValue());
}
}
else {
log.error("Was not able to find matching test {} in baseline session {}",testName,baselineId);
}
}
log.info("Making decision for test: {} (baseline session: {})",testName,workloadTask.getLimits().getBaselineId(sessionId));
// Compare
Set<DecisionPerLimit> decisionsPerLimit = new HashSet<DecisionPerLimit>();
Set<MetricEntity> duplicatedMetrics = new HashSet<MetricEntity>();
for (Limit limit : workloadTask.getLimits().getLimits()) {
DecisionPerLimit decisionPerLimit = compareMetricsToLimit(limit,
limitToEntity.get(limit),
duplicatedMetrics,
metricValues,metricIdToValuesBaseline,
workloadTask.getLimits().getLimitSetConfig());
log.debug(decisionPerLimit.toString());
decisionsPerLimit.add(decisionPerLimit);
}
// decisionPetTest = worst case decisionPerLimit
Decision decisionPerTest;
List<Decision> decisions = new ArrayList<Decision>();
for (DecisionPerLimit decisionPerLimit : decisionsPerLimit) {
decisions.add(decisionPerLimit.getDecisionPerLimit());
}
decisionPerTest = worstCaseDecisionMaker.getDecision(decisions);
DecisionPerTest resultForTest = new DecisionPerTest(testEntity, decisionsPerLimit, decisionPerTest);
log.info("\n{}",resultForTest.toString());
decisionsPerTest.add(resultForTest);
}
}
if (decisionsPerTest.size() > 0) {
// Save decisions per metric if there were any
saveDecisionsForMetrics(dataService, sessionId, taskId, decisionsPerTest);
// Save decisions per test
saveDecisionsForTests(dataService, decisionsPerTest);
// Call test group decision maker listener with basic or customer code
List<Provider<TestGroupDecisionMakerListener>> providers = ((CompositeTask) task).getDecisionMakerListeners();
if ((providers == null) ||
(providers.isEmpty())) {
// no decision maker defined in XML schema
// will use default one (basic)
providers = new ArrayList<Provider<TestGroupDecisionMakerListener>>();
providers.add(new BasicTGDecisionMakerListener());
}
TestGroupDecisionMakerListener decisionMakerListener = TestGroupDecisionMakerListener.Composer.compose(ProviderUtil.provideElements(providers,
sessionId,
taskId,
nodeContext,
JaggerPlace.TEST_GROUP_DECISION_MAKER_LISTENER));
TestGroupDecisionMakerInfo testGroupDecisionMakerInfo =
new TestGroupDecisionMakerInfo((CompositeTask)task,sessionId,decisionsPerTest);
Decision decisionPerTestGroup = decisionMakerListener.onDecisionMaking(testGroupDecisionMakerInfo);
// Save decisions per test group
log.info("\n\nDecision for test group {} - {}\n",task.getTaskName(),decisionPerTestGroup);
saveDecisionsForTestGroup(dataService, sessionId, taskId, decisionPerTestGroup);
}
}
}
private Set<MetricEntity> getMetricsForLimit(Limit limit, Map<String, MetricEntity> idToEntity) {
String metricId = limit.getMetricName();
Set<MetricEntity> metricsForLimit = new HashSet<MetricEntity>();
// Strict matching
if (idToEntity.keySet().contains(metricId)) {
metricsForLimit.add(idToEntity.get(metricId));
}
else {
// Matching to regex (f.e. agent name(s) or aggregator name(s) omitted)
String regex = "^" + metricId + ".*";
for (String id : idToEntity.keySet()) {
if (id.matches(regex)) {
metricsForLimit.add(idToEntity.get(id));
}
}
}
return metricsForLimit;
}
private DecisionPerLimit compareMetricsToLimit(Limit limit,
Set<MetricEntity> metricsPerLimit,
Set<MetricEntity> duplicatedMetrics,
Map<MetricEntity, MetricSummaryValueEntity> metricValues,
Map<String, Double> metricValuesBaseline,
LimitSetConfig limitSetConfig) {
Set<DecisionPerMetric> decisionsPerMetric = new HashSet<DecisionPerMetric>();
List<Decision> allDecisions = new ArrayList<Decision>();
Decision decisionWhenMetricWasAlreadyCompared = Decision.OK;
for (MetricEntity metricEntity : metricsPerLimit) {
Double refValue = limit.getRefValue();
Double value = metricValues.get(metricEntity).getValue();
Decision decision = Decision.OK;
// if metric entity already was used to take decision we will not use it
// 'limit to metric' relation should be 'one to many' or 'one to one'
if (duplicatedMetrics.contains(metricEntity)) {
String errorText = "Several limits are matching same metric. Decision for this metric was already taken and will be not overwritten. Metric: {},\n" +
"Decision due to error case: {}";
switch (limitSetConfig.getDecisionWhenSeveralLimitsMatchSingleMetric()) {
case OK:
decisionWhenMetricWasAlreadyCompared = Decision.OK;
log.info(errorText,metricEntity.toString(), decisionWhenMetricWasAlreadyCompared);
break;
case WARNING:
decisionWhenMetricWasAlreadyCompared = Decision.WARNING;
log.warn(errorText, metricEntity.toString(), decisionWhenMetricWasAlreadyCompared);
break;
default:
decisionWhenMetricWasAlreadyCompared = Decision.FATAL;
log.error(errorText, metricEntity.toString(), decisionWhenMetricWasAlreadyCompared);
break;
}
// case when several limits match single metrics should also influence final decision per limit
allDecisions.add(decisionWhenMetricWasAlreadyCompared);
continue;
}
duplicatedMetrics.add(metricEntity);
// if null - we are comparing to baseline
if (refValue == null) {
if (metricValuesBaseline.containsKey(metricEntity.getMetricId())) {
refValue = metricValuesBaseline.get(metricEntity.getMetricId());
}
}
if (refValue == null) {
String errorText = "Reference value for comparison of metric vs baseline was not found. Metric: {},\n" +
"Decision per metric: {}";
switch (limitSetConfig.getDecisionWhenNoBaselineForMetric()) {
case OK:
decision = Decision.OK;
log.info(errorText,metricEntity.toString(), decision);
break;
case WARNING:
decision = Decision.WARNING;
log.warn(errorText, metricEntity.toString(), decision);
break;
default:
decision = Decision.FATAL;
log.error(errorText, metricEntity.toString(), decision);
break;
}
} else {
if(refValue.equals(0D)){
if(value.equals(0D)){
decision = Decision.OK;
}else{
decision = Decision.FATAL;
}
log.warn("Limit thresholds are skipped due to refValue = 0. Limit: {}", limit);
}else {
Double rate = value / refValue;
if (rate < limit.getLowerErrorThreshold() || rate > limit.getUpperErrorThreshold()) {
decision = Decision.FATAL;
} else if (rate < limit.getLowerWarningThreshold() || rate > limit.getUpperWarningThreshold()) {
decision = Decision.WARNING;
} else {
decision = Decision.OK;
}
}
}
decisionsPerMetric.add(new DecisionPerMetric(metricEntity, value, refValue, decision));
}
// decisionPerLimit = worst case decisionPerLimit
Decision decisionPerLimit;
for (DecisionPerMetric decisionPerMetric : decisionsPerMetric) {
allDecisions.add(decisionPerMetric.getDecisionPerMetric());
log.info("\n{}",decisionPerMetric.toString());
}
if (allDecisions.isEmpty()) {
String errorText = "Limit doesn't have any matching metric in current session. Limit {},\n" +
"Decision per limit: {}";
switch (limitSetConfig.getDecisionWhenNoMetricForLimit()) {
case OK:
decisionPerLimit = Decision.OK;
log.debug(errorText,limit, decisionPerLimit);
break;
case WARNING:
decisionPerLimit = Decision.WARNING;
log.warn(errorText,limit, decisionPerLimit);
break;
default:
decisionPerLimit = Decision.FATAL;
log.error(errorText,limit, decisionPerLimit);
break;
}
}
else {
decisionPerLimit = worstCaseDecisionMaker.getDecision(allDecisions);
}
return new DecisionPerLimit(limit,decisionsPerMetric,decisionPerLimit);
}
private void saveDecisionsForMetrics(DefaultDataService defaultDataService, String sessionId, String testGroupTaskId, Collection<DecisionPerTest> decisionsPerTest) {
final List<DecisionPerMetricEntity> decisionPerMetricEntityList = new ArrayList<DecisionPerMetricEntity>();
Set<MetricDescriptionEntity> metricDescriptionEntitiesPerTest = null;
Set<MetricDescriptionEntity> metricDescriptionEntitiesPerTestGroup = null;
Long testGroupId = null;
for (DecisionPerTest decisionPerTest : decisionsPerTest) {
Long testId = decisionPerTest.getTestEntity().getId();
metricDescriptionEntitiesPerTest = getMetricDescriptionEntitiesPerTest(testId);
for (DecisionPerLimit decisionPerLimit : decisionPerTest.getDecisionsPerLimit()) {
for (DecisionPerMetric decisionPerMetric : decisionPerLimit.getDecisionsPerMetric()) {
String metricId = decisionPerMetric.getMetricEntity().getMetricId();
MetricDescriptionEntity metricDescriptionEntity = findMetricDescriptionByMetricId(metricId,metricDescriptionEntitiesPerTest);
// metric description belongs to parent (test-group) of the test
if (metricDescriptionEntity == null) {
if (testGroupId == null) {
testGroupId = defaultDataService.getDatabaseService().getTaskData(testGroupTaskId,sessionId).getId();
metricDescriptionEntitiesPerTestGroup = getMetricDescriptionEntitiesPerTest(testGroupId);
}
metricDescriptionEntity = findMetricDescriptionByMetricId(metricId, metricDescriptionEntitiesPerTestGroup);
}
if (metricDescriptionEntity != null) {
decisionPerMetricEntityList.add(new DecisionPerMetricEntity(metricDescriptionEntity,
decisionPerMetric.getDecisionPerMetric().toString()));
}
else {
log.error("\nUnable to create MetricDescriptionEntity for metricId " + metricId +
", testId " + testId +
", testGroupId " + testGroupId +
"\nMetric will influence decision making, but decision for this metric will be not saved to DB");
}
}
}
}
getHibernateTemplate().execute(new HibernateCallback<Void>() {
@Override
public Void doInHibernate(Session session) throws HibernateException, SQLException {
for (DecisionPerMetricEntity decisionPerMetricEntity : decisionPerMetricEntityList) {
session.persist(decisionPerMetricEntity);
}
session.flush();
return null;
}
});
}
private void saveDecisionsForTestGroup(DefaultDataService defaultDataService, String sessionId, String testGroupTaskId, Decision decisionsPerTestGroup) {
TaskData taskData = defaultDataService.getDatabaseService().getTaskData(testGroupTaskId,sessionId);
final DecisionPerTaskEntity decisionPerTaskEntity = new DecisionPerTaskEntity(taskData,decisionsPerTestGroup.toString());
getHibernateTemplate().execute(new HibernateCallback<Void>() {
@Override
public Void doInHibernate(Session session) throws HibernateException, SQLException {
session.persist(decisionPerTaskEntity);
session.flush();
return null;
}
});
}
private void saveDecisionsForTests(DefaultDataService defaultDataService, final Collection<DecisionPerTest> decisionsPerTest) {
Set<Long> testIds = new HashSet<Long>();
for (DecisionPerTest decisionPerTest : decisionsPerTest) {
testIds.add(decisionPerTest.getTestEntity().getId());
}
final Map<Long, TaskData> idsToTaskData = defaultDataService.getDatabaseService().getTaskData(testIds);
getHibernateTemplate().execute(new HibernateCallback<Void>() {
@Override
public Void doInHibernate(Session session) throws HibernateException, SQLException {
for (DecisionPerTest decisionPerTest : decisionsPerTest) {
session.persist(new DecisionPerTaskEntity(idsToTaskData.get(decisionPerTest.getTestEntity().getId()),
decisionPerTest.getDecisionPerTest().toString()));
}
session.flush();
return null;
}
});
}
private MetricDescriptionEntity findMetricDescriptionByMetricId (String metricId, Collection<MetricDescriptionEntity> metricDescriptionEntities) {
if (metricDescriptionEntities != null) {
for (MetricDescriptionEntity metricDescriptionEntity : metricDescriptionEntities) {
if (metricId.equals(metricDescriptionEntity.getMetricId())) {
return metricDescriptionEntity;
}
}
}
return null;
}
private Set<MetricDescriptionEntity> getMetricDescriptionEntitiesPerTest(final Long taskId) {
return getHibernateTemplate().execute(new HibernateCallback<Set<MetricDescriptionEntity>>() {
@Override
public Set<MetricDescriptionEntity> doInHibernate(org.hibernate.Session session) throws HibernateException, SQLException {
Set<MetricDescriptionEntity> result = new HashSet<MetricDescriptionEntity>();
result.addAll(session.createQuery("select m from MetricDescriptionEntity m where taskData.id=?")
.setParameter(0, taskId)
.list());
return result;
}
});
}
}