package com.intuit.tank.project; /* * #%L * JSF Support Beans * %% * Copyright (C) 2011 - 2015 Intuit Inc. * %% * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * #L% */ import java.math.BigDecimal; import java.text.NumberFormat; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map.Entry; import java.util.Set; import org.apache.commons.lang3.StringUtils; import com.intuit.tank.dao.DataFileDao; import com.intuit.tank.dao.JobNotificationDao; import com.intuit.tank.dao.JobRegionDao; import com.intuit.tank.harness.StopBehavior; import com.intuit.tank.logging.LoggingProfile; import com.intuit.tank.project.DataFile; import com.intuit.tank.project.EntityVersion; import com.intuit.tank.project.JobInstance; import com.intuit.tank.project.JobNotification; import com.intuit.tank.project.JobRegion; import com.intuit.tank.project.ScriptGroup; import com.intuit.tank.project.ScriptGroupStep; import com.intuit.tank.project.TestPlan; import com.intuit.tank.project.Workload; import com.intuit.tank.util.TestParamUtil; import com.intuit.tank.vm.api.enumerated.TerminationPolicy; import com.intuit.tank.vm.settings.TimeUtil; import com.intuit.tank.vm.settings.TankConfig; import com.intuit.tank.vm.settings.VmInstanceType; public class JobDetailFormatter { private static final String BREAK = "<br/>\n"; private static final long HOURS = 1000 * 60 * 60; public static String createJobDetails(JobValidator validator, Workload workload, JobInstance proposedJobInstance) { return buildDetails(validator, workload, proposedJobInstance, null); } public static String createJobDetails(JobValidator validator, String scriptName) { return buildDetails(validator, null, null, scriptName); } protected static String buildDetails(JobValidator validator, Workload workload, JobInstance proposedJobInstance, String scriptName) { StringBuilder sb = new StringBuilder(); StringBuilder errorSB = new StringBuilder(); TankConfig config = new TankConfig(); if (proposedJobInstance != null) { if (StringUtils.isBlank(proposedJobInstance.getName())) { addError(errorSB, "Name cannot be null"); } JobRegionDao jrd = new JobRegionDao(); List<JobRegion> regions = new ArrayList<JobRegion>(); for (EntityVersion ver : proposedJobInstance.getJobRegionVersions()) { JobRegion region = jrd.findById(ver.getObjectId()); if (region != null) { long users = TestParamUtil.evaluateExpression(region.getUsers(), proposedJobInstance.getExecutionTime(), proposedJobInstance.getSimulationTime(), proposedJobInstance.getRampTime()); if (users > 0) { regions.add(new JobRegion(region.getRegion(), Long.toString(users))); } } } Collections.sort(regions); long simulationTime = getSimulationTime(proposedJobInstance, workload, validator); addProperty(sb, "General Information", "", "emphasis"); addProperty(sb, "Name", StringUtils.isBlank(proposedJobInstance.getName()) ? "Name cannot be null" : proposedJobInstance.getName(), StringUtils.isBlank(proposedJobInstance.getName()) ? "error" : null); addProperty(sb, "Workload Type", proposedJobInstance.getIncrementStrategy().name()); addProperty(sb, "Tank Http Client", config.getAgentConfig().getTankClientName(proposedJobInstance.getTankClientClass())); addProperty(sb, "Agent VM Type", getVmDetails(config, proposedJobInstance.getVmInstanceType())); addProperty(sb, "Assign Elastic Ips", Boolean.toString(proposedJobInstance.isUseEips())); addProperty(sb, "Max Users per Agent", Integer.toString(proposedJobInstance.getNumUsersPerAgent())); addProperty(sb, "Estimated Cost", calculateCost(config, proposedJobInstance, regions, simulationTime)); addProperty(sb, "Location", proposedJobInstance.getLocation()); addProperty(sb, "Logging Profile", LoggingProfile.fromString(proposedJobInstance.getLoggingProfile()) .getDisplayName()); addProperty(sb, "Stop Behavior", StopBehavior.fromString(proposedJobInstance.getStopBehavior()) .getDisplay()); addProperty(sb, "Run Scripts Until", proposedJobInstance.getTerminationPolicy().getDisplay(), proposedJobInstance.getTerminationPolicy() == TerminationPolicy.time && proposedJobInstance.getSimulationTime() == 0 ? "error" : null); sb.append(BREAK); addProperty( sb, "Simulation Time", TimeUtil.toTimeString(simulationTime)); if (proposedJobInstance.getTerminationPolicy() == TerminationPolicy.time && proposedJobInstance.getSimulationTime() == 0) { addError(errorSB, "Simulation time not set."); } addProperty(sb, "Ramp Time", TimeUtil.toTimeString(proposedJobInstance.getRampTime())); addProperty(sb, "Initial Users", Integer.toString(proposedJobInstance.getBaselineVirtualUsers())); addProperty(sb, "User Increment", Integer.toString(proposedJobInstance.getUserIntervalIncrement())); // users and regions sb.append(BREAK); addProperty(sb, "Total Users", Integer.toString(proposedJobInstance.getTotalVirtualUsers()), proposedJobInstance.getTotalVirtualUsers() == 0 ? "error" : "emphasis"); if (proposedJobInstance.getTotalVirtualUsers() == 0) { addError(errorSB, "No users defined."); } for (JobRegion r : regions) { if (config.getStandalone()) { addProperty(sb, " Users", r.getUsers()); } else { addProperty(sb, " " + r.getRegion().getDescription(), r.getUsers()); } } sb.append(BREAK); sb.append(BREAK); int userPercentage = 0; for (TestPlan plan : workload.getTestPlans()) { userPercentage += plan.getUserPercentage(); } if (userPercentage != 100) { addError(errorSB, "User Percentage of Test Plans does not add up to 100%"); } // datafiles addProperty(sb, "Data Files", proposedJobInstance.getDataFileVersions().size() == 0 ? "None" : null, "emphasis"); DataFileDao dfd = new DataFileDao(); Set<String> datafiles = new HashSet<String>(); for (EntityVersion ver : proposedJobInstance.getDataFileVersions()) { DataFile df = dfd.findById(ver.getObjectId()); if (df != null) { addProperty(sb, " " + df.getPath(), null); datafiles.add(df.getPath()); } else { addProperty(sb, " " + ver.getObjectId(), "data file not found.", "error"); } } sb.append(BREAK); // variables addProperty(sb, "Global Variables", proposedJobInstance.getVariables().size() == 0 ? "None" : " (Allow Overide: " + proposedJobInstance.isAllowOverride() + ")", "emphasis"); for (Entry<String, String> entry : proposedJobInstance.getVariables().entrySet()) { addProperty(sb, " " + entry.getKey(), entry.getValue()); if (entry.getValue().toLowerCase().endsWith(".csv") && !datafiles.contains(entry.getValue())) { addProperty(sb, " WARNING", "This variable, " + entry.getKey() + " appears to be a reference to a datafile, " + entry.getValue() + " that is not declared.", "error"); } } sb.append(BREAK); // notifications addProperty(sb, "Notifications", proposedJobInstance.getNotificationVersions().size() == 0 ? "None" : null, "emphasis"); JobNotificationDao jnd = new JobNotificationDao(); for (EntityVersion ver : proposedJobInstance.getNotificationVersions()) { JobNotification not = jnd.findById(ver.getObjectId()); if (not != null) { if (not.getLifecycleEvents().size() > 0) { addProperty(sb, " " + not.getRecipientList(), StringUtils.join(not.getLifecycleEvents(), ", ")); } else { addProperty(sb, " " + not.getRecipientList(), "no events selected", "error"); } } } sb.append(BREAK); addProperty(sb, "Scripts", "", "emphasis"); // scripts List<ScriptGroupStep> stepsList = new ArrayList<ScriptGroupStep>(); for (TestPlan plan : workload.getTestPlans()) { int numUsers = plan.getUserPercentage() > 0 ? proposedJobInstance.getTotalVirtualUsers() : 0; if (plan.getUserPercentage() < 100 && plan.getUserPercentage() > 0) { numUsers = (int) Math.floor(numUsers * ((double) plan.getUserPercentage() / 100D)); } addProperty( sb, " " + plan.getName(), plan.getUserPercentage() + "% : (" + numUsers + " users) : estimated Time " + TimeUtil.toTimeString(validator.getExpectedTime(plan.getName())), userPercentage != 100 ? "error" : null); if (plan.getScriptGroups().size() == 0) { addProperty(sb, " " + plan.getName(), "contains no script groups", "error"); } for (ScriptGroup group : plan.getScriptGroups()) { addProperty(sb, " " + group.getName(), "loop " + group.getLoop() + " time(s)"); if (group.getScriptGroupSteps().size() == 0) { addProperty(sb, " " + group.getName(), "contains no scripts", "error"); } for (ScriptGroupStep s : group.getScriptGroupSteps()) { stepsList.add(s); addProperty(sb, " " + s.getScript().getName(), "loop " + s.getLoop() + " time(s)"); } } } sb.append(BREAK); if (stepsList.size() == 0) { addError(errorSB, "No scripts defined."); } } else { addProperty(sb, scriptName, "Estimated Time " + validator.getDuration(scriptName), "emphasis"); sb.append(BREAK); sb.append(BREAK); } addProperty(sb, "Variable Validation", "", "emphasis"); addProperty(sb, " Declared Variables", "", "emphasis"); for (Entry<String, Set<String>> entry : validator.getDeclaredVariables().entrySet()) { for (String value : entry.getValue()) { addProperty(sb, " " + entry.getKey(), value, validator.isSuperfluous(entry.getKey()) ? "error" : null); } } sb.append(BREAK); addProperty(sb, " Used Variables", "", "emphasis"); for (String s : validator.getUsedVariables()) { addProperty(sb, " ", s, validator.isOrphaned(s) ? "error" : null); } sb.append(BREAK); if (validator.isProcessAssignements()) { addProperty(sb, " Assignements", "", "emphasis"); for (Entry<String, Set<String>> entry : validator.getAssignments().entrySet()) { for (String value : entry.getValue()) { addProperty(sb, " " + entry.getKey(), value, validator.isSuperfluous(entry.getKey()) ? "error" : null); } } sb.append(BREAK); } sb.append(BREAK); if (!validator.getBestPracticeViolations().isEmpty()) { StringBuilder tsb = new StringBuilder(); addProperty(tsb, "Best Practice Violations", "", "emphasis"); for (String s : validator.getBestPracticeViolations()) { addError(tsb, s); } tsb.append(BREAK); tsb.append(BREAK); sb.insert(0, tsb.toString()); } if (errorSB.length() > 0) { sb = new StringBuilder().append("ERRORS").append(BREAK).append(errorSB.append(BREAK).toString()) .append(sb.toString()); } return sb.toString(); } protected static long getSimulationTime(JobInstance proposedJobInstance, Workload workload, JobValidator validator) { long ret = proposedJobInstance.getSimulationTime(); if (TerminationPolicy.script == proposedJobInstance.getTerminationPolicy()) { ret = 0; for (TestPlan plan : workload.getTestPlans()) { ret = Math.max(ret, validator.getExpectedTime(plan.getName())); } } return ret; } protected static String calculateCost(TankConfig config, JobInstance proposedJobInstance, List<JobRegion> regions, long simulationTime) { List<VmInstanceType> instanceTypes = config.getVmManagerConfig().getInstanceTypes(); BigDecimal costPerHour = new BigDecimal(.5D); for (VmInstanceType type : instanceTypes) { if (type.getName().equals(proposedJobInstance.getVmInstanceType())) { costPerHour = new BigDecimal(type.getCost()); break; } } long time = simulationTime + proposedJobInstance.getRampTime(); int numMachines = 0; for (JobRegion region : regions) { int users = Integer.parseInt(region.getUsers()); if (users > 0) { numMachines += (int) Math.ceil((double) users / (double) proposedJobInstance.getNumUsersPerAgent()); } } // dynamoDB costs about 1.5 times the instance cost BigDecimal cost = estimateCost(numMachines, costPerHour, time); NumberFormat nf = NumberFormat.getCurrencyInstance(Locale.US); return nf.format(cost.doubleValue()); } protected static BigDecimal estimateCost(int numInstances, BigDecimal costPerHour, long time) { BigDecimal cost = BigDecimal.ZERO; // calculate the number of machines and the expected run time BigDecimal hours = new BigDecimal(Math.max(1, Math.ceil(time / HOURS))); cost = cost.add(costPerHour.multiply(new BigDecimal(numInstances)).multiply(hours)); // dynamoDB costs about 1.5 times the instance cost cost = cost.add(cost.multiply(new BigDecimal(1.5D))); return cost; } protected static String getVmDetails(TankConfig config, String vmInstanceType) { List<VmInstanceType> instanceTypes = config.getVmManagerConfig().getInstanceTypes(); StringBuilder sb = new StringBuilder(); sb.append(vmInstanceType); for (VmInstanceType type : instanceTypes) { if (type.getName().equals(vmInstanceType)) { sb.append(" (cpus=").append(type.getCpus()) .append(" ecus=").append(type.getEcus()) .append(" memory=").append(type.getMemory()) .append(" cost=$").append(type.getCost()).append(" per hour)"); break; } } return sb.toString(); } protected static void addProperty(StringBuilder sb, String key, String value) { addProperty(sb, key, value, null); } private static void addProperty(StringBuilder sb, String key, String value, String style) { if (StringUtils.isNotBlank(style)) { sb.append("<span style=\"font-weight: bold;\">"); } sb.append(StringUtils.replace(key, " ", " ")); if (StringUtils.isNotBlank(style)) { sb.append("</span>"); } if (StringUtils.isNotBlank(value)) { if (StringUtils.isNotBlank(key)) { sb.append(": "); } if (StringUtils.isNotBlank(style)) { sb.append("<span class=\"" + style + "\">"); } sb.append(value); if (StringUtils.isNotBlank(style)) { sb.append("</span>"); } } sb.append(BREAK); } protected static void addError(StringBuilder sb, String message) { sb.append("<span class=\"error\" style=\"font-weight: bold;\">"); sb.append(message); sb.append("</span>"); sb.append(BREAK); } }