
com.xceptance.xlt.mastercontroller.BasicConsoleUI Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of xlt Show documentation
Show all versions of xlt Show documentation
XLT (Xceptance LoadTest) is an extensive load and performance test tool developed and maintained by Xceptance.
/*
* Copyright (c) 2005-2023 Xceptance Software Technologies GmbH
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.xceptance.xlt.mastercontroller;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Formatter;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.MutablePair;
import org.apache.commons.lang3.tuple.Pair;
import com.xceptance.common.util.ConsoleUiUtils;
import com.xceptance.common.util.ProductInformation;
import com.xceptance.xlt.agentcontroller.AgentController;
import com.xceptance.xlt.agentcontroller.AgentStatus;
import com.xceptance.xlt.agentcontroller.TestResultAmount;
import com.xceptance.xlt.agentcontroller.TestUserStatus;
import com.xceptance.xlt.util.AgentControllerException;
import com.xceptance.xlt.util.AgentControllerInfo;
import com.xceptance.xlt.util.FailedAgentControllerCollection;
/**
* The BasicConsoleUI is the base class for all master controller user interfaces.
*/
public abstract class BasicConsoleUI implements MasterControllerUI
{
private static final String COPYRIGHT = "Copyright (c) 2005-%s %s. All rights reserved.";
private static final List TEST_RESULT_AMOUNT_DISPLAY_NAMES = Arrays.asList(TestResultAmount.displayNames());
private static final List TEST_RESULT_AMOUNTS = Arrays.asList(TestResultAmount.values());
private static final List TEST_RESULT_SHORTCUTS = Arrays.asList(TestResultAmount.shortcuts());
private static final List REPORT_CREATION_TYPE_SHORTCUTS = Arrays.asList(ReportCreationType.shortcuts());
private static final List REPORT_CREATION_TYPE_DISPLAY_NAMES = Arrays.asList(ReportCreationType.displayNames());
private static final List REPORT_CREATION_TYPES = Arrays.asList(ReportCreationType.values());
private static final String TIME_TOTALS = "/";
private static final String SETTING_NOT_AVAILABLE = "n/a";
private static final String OVERFLOW_ERROR = "#OUT OF RANGE#";
/**
* The master controller this user interface interacts with.
*/
protected final MasterController masterController;
/**
* Whether the test status is shown per user or per user type.
*/
private boolean showDetailedStatusList;
/**
* Interval in seconds to update the agent status list.
*/
private int statusListUpdateInterval;
/**
* Creates a new BasicConsoleUI object.
*
* @param masterController
* the master controller to use
*/
public BasicConsoleUI(final MasterController masterController)
{
this.masterController = masterController;
}
/**
* {@inheritDoc}
*/
@Override
public void agentFilesUploaded()
{
print();
}
/**
* {@inheritDoc}
*/
@Override
public void agentsStarted()
{
print();
}
/**
* {@inheritDoc}
*/
@Override
public void agentsStopped()
{
print();
}
/**
* {@inheritDoc}
*/
@Override
public void agentStatusReceived(final FailedAgentControllerCollection results)
{
if (!results.isEmpty())
{
for (final Map.Entry result : results.getMap().entrySet())
{
final Exception ex = result.getValue();
if (ex != null)
{
final String msg = getUserFriendlyExceptionMessage(ex);
System.out.println("Failed to get agent status from " + result.getKey() + " -> " + msg);
}
}
System.out.println();
}
}
/**
* {@inheritDoc}
*/
@Override
public void downloadingTestResults()
{
System.out.print(" -> Downloading test results");
}
/**
* Triggers the master controller to download the test results from all known agent controllers.
*
* @param testResultAmount
* the amount of test result data to download
* @return true
if the operation was successful for all agent controllers; false
otherwise
*/
public boolean downloadTestResults(TestResultAmount testResultAmount)
{
if (testResultAmount == null)
{
testResultAmount = ConsoleUiUtils.selectItem("Select the data to be downloaded:", TEST_RESULT_SHORTCUTS,
TEST_RESULT_AMOUNT_DISPLAY_NAMES, TEST_RESULT_AMOUNTS);
System.out.println();
}
boolean ok = !TestResultAmount.CANCEL.equals(testResultAmount);
if (ok)
{
System.out.println("Downloading test results... Please be patient, it might take some time...");
ok = masterController.downloadTestResults(testResultAmount);
System.out.println();
if (ok)
{
handleTestComment();
System.out.println("\nResults have been downloaded to: " + masterController.getCurrentTestResultsDirectory());
}
}
return ok;
}
/**
* Triggers the master controller to generate the test report from the downloaded results and queries the user for
* time range option.
*
* @return true
if the operation was successful; false
otherwise
*/
public boolean generateReport()
{
return generateReport(null);
}
/**
* Triggers the master controller to generate the test report from the downloaded results.
*
* @param reportCreationType
* time range option to generate the report from or null
to query the user
* @return true
if the operation was successful; false
otherwise
*/
public boolean generateReport(ReportCreationType reportCreationType)
{
boolean result = false;
if (reportCreationType == null)
{
reportCreationType = ConsoleUiUtils.selectItem("Would you like to include the ramp-up period in the test report?",
REPORT_CREATION_TYPE_SHORTCUTS, REPORT_CREATION_TYPE_DISPLAY_NAMES,
REPORT_CREATION_TYPES);
System.out.println();
}
if (!reportCreationType.equals(ReportCreationType.ABORT))
{
System.out.println("Generating load test report based on latest download...");
result = masterController.generateReport(reportCreationType);
if (!result)
{
System.out.println(" -> Failed");
}
System.out.println();
}
return result;
}
/**
* {@inheritDoc}
*/
@Override
public int getStatusListUpdateInterval()
{
return statusListUpdateInterval;
}
/**
* Checks whether there is a running load test.
*
* @return true
if there is a running load test; false
otherwise
*/
public boolean isLoadTestRunning()
{
// check whether there are still running agents
try
{
if (masterController.isAnyAgentRunning())
{
System.out.println("-> WARNING: Unable to execute command because a test is currently running.");
return true;
}
}
catch (final AgentControllerException e)
{
// potentially there are running agents even if we can not communicate with the agent controller.
System.out.println("-> WARNING: Unable to execute command because at least one agent controller cannot be reached.");
return true;
}
return false;
}
/**
* Returns whether to display detailed status information for each simulated test user, or whether status
* information will be aggregated into one line per user type.
*
* @return whether to show detailed information
*/
public boolean isShowDetailedStatusList()
{
return showDetailedStatusList;
}
/**
* Prints the status of all agents to the console in a tabular format.
*/
public void printAgentStatusList()
{
if (showDetailedStatusList)
{
printDetailedAgentStatusList();
}
else
{
printCondensedAgentStatusList();
}
}
/**
* Prints the status of all agents to the console in a tabular format. The output contains one line per simulated
* user type.
*/
public void printCondensedAgentStatusList()
{
final Set agentStatusList = masterController.getAgentStatusList();
final Map> badHostAgents = getFailedAgentStatusMap(agentStatusList);
final List userTypeStatusList = getTestUserTypeStatusList(agentStatusList);
if (!userTypeStatusList.isEmpty())
{
// determine longest user name
int maxNameLength = 9;
for (TestUserTypeStatus testUserTypeStatus : userTypeStatusList)
{
maxNameLength = Math.max(maxNameLength, testUserTypeStatus.getUserName().length());
}
// format and append the header line
final StringBuilder buf = new StringBuilder();
final Formatter formatter = new Formatter(buf);
final String headerFormat = "%-" + maxNameLength +
"s State Running Users Iterations Last Time Avg. Time Elapsed Time Events Errors" +
" Progress\n";
formatter.format(headerFormat, "Test Case");
final String separatorLineFormat = "%s -------- ---------------- ---------- --------- --------- ------------ --------- -------------" +
" --------\n";
formatter.format(separatorLineFormat, StringUtils.repeat("-", maxNameLength));
// format and append the user type lines
final String format = "%-" + maxNameLength + "s %-8s %,6d of %,6d %,10d %9s %9s %10s %,9d %,6d %6s" +
" %7d%%\n";
final String timeFormat = "%,7.2f s";
final String failedFormat = "%-" + maxNameLength + "s %-8s %s\n";
for (final TestUserTypeStatus userTypeStatus : userTypeStatusList)
{
if (userTypeStatus.getState() == TestUserStatus.State.Failed)
{
formatter.format(failedFormat, userTypeStatus.getUserName(), userTypeStatus.getState(), userTypeStatus.getException());
}
else
{
final String elapsedTime = formatTime(userTypeStatus.getElapsedTime());
int errorRate = 0;
if (userTypeStatus.getIterations() > 0)
{
errorRate = Math.round(userTypeStatus.getErrors() * 100.0f / userTypeStatus.getIterations());
}
final double lastRuntime = userTypeStatus.getLastRuntime() / 1000.0;
final double avgRuntime = userTypeStatus.getAverageRuntime() / 1000.0;
formatter.format(format, userTypeStatus.getUserName(), userTypeStatus.getState(), userTypeStatus.getRunningUsers(),
userTypeStatus.getTotalUsers(), userTypeStatus.getIterations(), String.format(timeFormat, lastRuntime),
String.format(timeFormat, avgRuntime), elapsedTime, userTypeStatus.getEvents(),
userTypeStatus.getErrors(), "(" + errorRate + "%)", userTypeStatus.getPercentageComplete());
}
}
// format and append the total status line
if (userTypeStatusList.size() > 1)
{
TestUserTypeStatus summaryStatus = getTotalStatus(userTypeStatusList);
final String elapsedTime = formatTime(summaryStatus.getElapsedTime());
int iterations = summaryStatus.getIterations();
int errors = summaryStatus.getErrors();
int errorRate = (iterations > 0) ? Math.round(errors * 100.0f / iterations) : 0;
formatter.format(separatorLineFormat, StringUtils.repeat("-", maxNameLength));
formatter.format(format, summaryStatus.getUserName(), summaryStatus.getState(), summaryStatus.getRunningUsers(),
summaryStatus.getTotalUsers(), iterations, TIME_TOTALS, TIME_TOTALS, elapsedTime,
summaryStatus.getEvents(), errors, "(" + errorRate + "%)", summaryStatus.getPercentageComplete());
}
// print the status
formatter.close();
System.out.println(buf);
}
if (!badHostAgents.isEmpty())
{
final StringBuilder sb = new StringBuilder();
int count = 0;
sb.append("-> Agent(s) exited unexpectedly:\n");
for (final Entry> badAgents : badHostAgents.entrySet())
{
sb.append(" - ").append(badAgents.getKey()).append("\n");
final Set badAgentsOfHost = badAgents.getValue();
for (final AgentStatus badAgentStatus : badAgentsOfHost)
{
sb.append(" Agent '").append(badAgentStatus.getAgentID()).append("' returned with exit code '")
.append(badAgentStatus.getErrorExitCode()).append("'\n");
}
count += badAgentsOfHost.size();
}
sb.insert(3, Integer.toString(count));
System.out.println(sb.toString());
}
}
/**
* Returns the total status calculated from the individual user-type-specific status objects.
*
* @param userTypeStatusList
* the list of user type status objects
* @return the total status
*/
protected TestUserTypeStatus getTotalStatus(List userTypeStatusList)
{
// the initial status values
int errors = 0, events = 0, iterations = 0, runningUsers = 0, totalUsers = 0, percentage = 0;
long lastRuntime = 0, totalRuntime = 0;
TestUserStatus.State state = TestUserStatus.State.Waiting;
long elapsed = Long.MIN_VALUE;
long lastModified = 0;
// update the status values
for (final TestUserTypeStatus userTypeStatus : userTypeStatusList)
{
if (state != TestUserStatus.State.Running && userTypeStatus.getState() != TestUserStatus.State.Waiting)
{
state = userTypeStatus.getState();
}
if (lastModified < userTypeStatus.getLastModifiedDate())
{
lastModified = userTypeStatus.getLastModifiedDate();
lastRuntime = userTypeStatus.getLastRuntime();
}
errors += userTypeStatus.getErrors();
events += userTypeStatus.getEvents();
iterations += userTypeStatus.getIterations();
totalRuntime += userTypeStatus.getTotalRuntime();
runningUsers += userTypeStatus.getRunningUsers();
totalUsers += userTypeStatus.getTotalUsers();
percentage += userTypeStatus.getPercentageComplete();
elapsed = Math.max(elapsed, userTypeStatus.getElapsedTime());
}
// "fix" certain status values depending on the count of input status objects
final int count = userTypeStatusList.size();
if (count == 0)
{
elapsed = 0;
}
else if (count > 1)
{
percentage = percentage / count;
}
// finally build the total status
final TestUserTypeStatus summaryStatus = new TestUserTypeStatus();
summaryStatus.setUserName("Totals");
summaryStatus.setState(state);
summaryStatus.setRunningUsers(runningUsers);
summaryStatus.setTotalUsers(totalUsers);
summaryStatus.setIterations(iterations);
summaryStatus.setLastRuntime(lastRuntime);
summaryStatus.setTotalRuntime(totalRuntime);
summaryStatus.setEvents(events);
summaryStatus.setErrors(errors);
summaryStatus.setElapsedTime(elapsed);
summaryStatus.setPercentageComplete(percentage);
return summaryStatus;
}
/**
* Returns a list of user-type-specific status objects calculated from each individual user-specific status of a
* certain user type.
*
* @param agentStatusList
* the list of agent status objects containing the user-specific status objects
* @return the list of user type status objects
*/
protected List getTestUserTypeStatusList(final Set agentStatusList)
{
final Map userTypeStatusList = new TreeMap();
for (final AgentStatus agentStatus : agentStatusList)
{
for (final TestUserStatus userStatus : agentStatus.getTestUserStatusList())
{
// get the base name from the user name
final String testCaseName = StringUtils.substringBeforeLast(userStatus.getUserName(), "-");
// maintain one global status for all users of the same type
TestUserTypeStatus userTypeStatus = userTypeStatusList.get(testCaseName);
if (userTypeStatus == null)
{
userTypeStatus = new TestUserTypeStatus();
userTypeStatus.setUserName(testCaseName);
userTypeStatus.setStartDate(Long.MAX_VALUE);
userTypeStatus.setElapsedTime(Long.MIN_VALUE);
userTypeStatusList.put(testCaseName, userTypeStatus);
}
// maintain count of total and running users
userTypeStatus.setTotalUsers(userTypeStatus.getTotalUsers() + 1);
if (userStatus.getState() == TestUserStatus.State.Running)
{
userTypeStatus.setRunningUsers(userTypeStatus.getRunningUsers() + 1);
}
// determine the over-all running state
TestUserStatus.State globalState = userTypeStatus.getState();
final TestUserStatus.State state = userStatus.getState();
if (globalState == TestUserStatus.State.Running)
{
// globalState = TestUserStatus.State.Running;
}
else if (state != TestUserStatus.State.Waiting)
{
globalState = state;
}
userTypeStatus.setState(globalState);
// take the last run time from the most recently updated status
if (userTypeStatus.getLastModifiedDate() < userStatus.getLastModifiedDate())
{
userTypeStatus.setLastModifiedDate(userStatus.getLastModifiedDate());
userTypeStatus.setLastRuntime(userStatus.getLastRuntime());
}
// calculate the overall percentage
if (userStatus.getMode() == TestUserStatus.Mode.TIME_PERIOD)
{
// for duration-based tests take the maximum
userTypeStatus.setPercentageComplete(Math.max(userTypeStatus.getPercentageComplete(),
userStatus.getPercentageComplete()));
}
else
{
// for iteration-based tests take the mean
final int totalUsers = userTypeStatus.getTotalUsers();
if (totalUsers == 1)
{
// this is the initial value
userTypeStatus.setPercentageComplete(userStatus.getPercentageComplete());
}
else
{
// incrementally update the mean value
double mean = userTypeStatus.getPercentageComplete();
mean = mean + (userStatus.getPercentageComplete() - mean) / totalUsers;
userTypeStatus.setPercentageComplete((int) mean);
}
}
// update the remaining values
userTypeStatus.setIterations(userTypeStatus.getIterations() + userStatus.getIterations());
userTypeStatus.setTotalRuntime(userTypeStatus.getTotalRuntime() + userStatus.getTotalRuntime());
userTypeStatus.setEvents(userTypeStatus.getEvents() + userStatus.getEvents());
userTypeStatus.setErrors(userTypeStatus.getErrors() + userStatus.getErrors());
userTypeStatus.setException(userStatus.getException());
userTypeStatus.setStartDate(Math.min(userTypeStatus.getStartDate(), userStatus.getStartDate()));
userTypeStatus.setElapsedTime(Math.max(userTypeStatus.getElapsedTime(), userStatus.getElapsedTime()));
}
}
return new ArrayList<>(userTypeStatusList.values());
}
/**
* Returns the status objects for only those agents that exited with an error.
*
* @param agentStatusList
* the list of all agent status objects
* @return the failed agent status objects keyed by host name
*/
protected Map> getFailedAgentStatusMap(final Set agentStatusList)
{
final Map> failedAgentStatusListByHost = new HashMap>();
for (final AgentStatus agentStatus : agentStatusList)
{
if (agentStatus.getErrorExitCode() != 0)
{
Set failedAgentStatusList = failedAgentStatusListByHost.get(agentStatus.getHostName());
if (failedAgentStatusList == null)
{
failedAgentStatusList = new HashSet();
failedAgentStatusListByHost.put(agentStatus.getHostName(), failedAgentStatusList);
}
failedAgentStatusList.add(agentStatus);
}
}
return failedAgentStatusListByHost;
}
/**
* Prints the status of all agents to the console in a tabular format. The output contains one line per simulated
* test user.
*/
public void printDetailedAgentStatusList()
{
final Set agentStatusList = masterController.getAgentStatusList();
// determine the maximum length of user names
int maxNameLength = 4;
int maxHostNameLength = 15;
boolean nonEmpty = false;
for (final AgentStatus agentStatus : agentStatusList)
{
final List testUserStatusList = agentStatus.getTestUserStatusList();
nonEmpty |= (!testUserStatusList.isEmpty());
for (final TestUserStatus userStatus : testUserStatusList)
{
maxNameLength = Math.max(maxNameLength, userStatus.getUserName().length());
}
maxHostNameLength = Math.max(maxHostNameLength, agentStatus.getHostName().length());
}
if (!nonEmpty)
{
return;
}
// format and append the header line
final StringBuilder buf = new StringBuilder();
final Formatter formatter = new Formatter(buf);
final String headerFormat = "%-" + maxHostNameLength + "s %-" + maxNameLength +
"s State Iterations Last Time Avg. Time Elapsed Time Events Errors Progress\n";
formatter.format(headerFormat, "Agent Host", "User");
final String separatorLineFormat = "%s %s -------- ---------- --------- --------- ------------ --------- ------------- --------\n";
formatter.format(separatorLineFormat, StringUtils.repeat("-", maxHostNameLength), StringUtils.repeat("-", maxNameLength));
// format and append the user lines
final String format = "%-" + maxHostNameLength + "s %-" + maxNameLength +
"s %-8s %,10d %,7.2f s %,7.2f s %10s %,9d %,6d %6s %7d%%\n";
final String failedFormat = "%-" + maxHostNameLength + "s %-" + maxNameLength + "s %-8s *** %s ***\n";
for (final AgentStatus agentStatus : agentStatusList)
{
for (final TestUserStatus userStatus : agentStatus.getTestUserStatusList())
{
if (userStatus.getState() == TestUserStatus.State.Failed)
{
formatter.format(failedFormat, agentStatus.getHostName(), userStatus.getUserName(), userStatus.getState(),
userStatus.getException());
}
else
{
final String elapsedTime = formatTime(userStatus.getElapsedTime());
int errorRate = 0;
if (userStatus.getIterations() > 0)
{
errorRate = Math.round(userStatus.getErrors() * 100.0f / userStatus.getIterations());
}
formatter.format(format, agentStatus.getHostName(), userStatus.getUserName(), userStatus.getState(),
userStatus.getIterations(), userStatus.getLastRuntime() / 1000.0,
userStatus.getAverageRuntime() / 1000.0, elapsedTime, userStatus.getEvents(), userStatus.getErrors(),
"(" + errorRate + "%)", userStatus.getPercentageComplete());
}
}
}
// print the status
formatter.close();
System.out.println(buf);
}
public void printLoadTestSettings()
{
// Get the current load profile.
final String output = getLoadTestSettings(masterController.getCurrentLoadProfile());
if (!StringUtils.isBlank(output))
{
System.out.println();
System.out.println("Load Profile");
System.out.println(output);
}
}
/**
* This method returns a summary of the current load test settings.
*
* @param loadTestSettings
* The load test settings that should be analyzed.
* @return A String containing the settings for all configured load profiles. In case no load profile is configured
* an empty String ("") will be returned.
*/
public String getLoadTestSettings(final TestLoadProfileConfiguration loadTestSettings)
{
// If the mastercontroller has not parsed the configuration yet, we quit quietly.
final StringBuilder configLine = new StringBuilder();
if (loadTestSettings != null)
{
final Formatter formatter = new Formatter(configLine);
// Prepare the table header
final String testCaseColumnHeading = "Test Case";
final String arrivalRateColumnHeading = "Arrival Rate [eff]";
final String usersColumnHeading = "Users [eff]";
final String loadFactorColumnHeading = "Load Factor";
final String measurementPeriodColumnHeading = "Measurement Period";
final String colSep = " | ";
final Map maxLengthMap = getColumnBoundaries(loadTestSettings);
// Check what's longer: column heading or cell-content.
final int maxNameLength = Math.max(maxLengthMap.get("testCases"), testCaseColumnHeading.length());
final int arrivalLength = Math.max(maxLengthMap.get("arrivalRate"), arrivalRateColumnHeading.length());
final int maxUserLength = Math.max(maxLengthMap.get("users"), usersColumnHeading.length());
final int maxLoadFactor = Math.max(maxLengthMap.get("loadFactor"), loadFactorColumnHeading.length());
final int measureLength = measurementPeriodColumnHeading.length();
final int totalLength = maxNameLength + arrivalLength + maxUserLength + maxLoadFactor + measureLength + 4 * colSep.length();
final String lineFormat = StringUtils.joinWith(colSep, "%s", "%s", "%s", "%s", "%s\n");
final String dashLine = StringUtils.repeat("-", totalLength);
formatter.format("%s\n", dashLine);
formatter.format(lineFormat, StringUtils.center(testCaseColumnHeading, maxNameLength),
StringUtils.center(arrivalRateColumnHeading, arrivalLength),
StringUtils.center(usersColumnHeading, maxUserLength),
StringUtils.center(loadFactorColumnHeading, maxLoadFactor),
StringUtils.center(measurementPeriodColumnHeading, measureLength));
formatter.format("%s\n", dashLine);
int arrivalRateSum = 0, numberOfUsersSum = 0, measurementPeriodMaximum = 0;
String previousLoadFactor = "";
boolean sameLoadFactor = true, arrivalRateOverflow = false, userOverflow = false;
for (TestCaseLoadProfileConfiguration settings : loadTestSettings.getLoadTestConfiguration())
{
// Arrival Rate
Pair boundaries = getMinMaxValue(settings.getArrivalRate());
if (!arrivalRateOverflow && boundaries != null)
{
try
{
arrivalRateSum = Math.addExact(arrivalRateSum, boundaries.getRight());
}
catch (ArithmeticException e)
{
// We exceeded the maximum integer boundaries
arrivalRateOverflow = true;
}
}
final String arrivalRate = getIntRangeAsString(boundaries);
// Number of Users
boundaries = getMinMaxValue(settings.getNumberOfUsers());
if (!userOverflow && boundaries != null)
{
try
{
numberOfUsersSum = Math.addExact(numberOfUsersSum, boundaries.getRight());
}
catch (ArithmeticException e)
{
userOverflow = true;
}
}
final String users = getIntRangeAsString(boundaries);
// Load Factor
boundaries = getMinMaxValue(settings.getLoadFactor());
final String loadFactor;
if (boundaries != null)
{
final double minLoadFactorFraction = boundaries.getLeft() / 1000.0;
final double maxLoadFactorFraction = boundaries.getRight() / 1000.0;
loadFactor = getDoubleRangeAsString(new MutablePair(minLoadFactorFraction, maxLoadFactorFraction));
}
else
{
loadFactor = getDoubleRangeAsString(null);
}
if (sameLoadFactor)
{
// At the beginning we need to initialize previousLoadFactor
if (StringUtils.isEmpty(previousLoadFactor))
{
previousLoadFactor = loadFactor;
}
else
{
sameLoadFactor = previousLoadFactor.equals(loadFactor);
}
}
int measurementPeriod = settings.getMeasurementPeriod();
measurementPeriodMaximum = Math.max(measurementPeriod, measurementPeriodMaximum);
formatter.format(lineFormat, StringUtils.rightPad(settings.getUserName(), maxNameLength),
StringUtils.leftPad(arrivalRate, arrivalLength), StringUtils.leftPad(users, maxUserLength),
StringUtils.leftPad(loadFactor, maxLoadFactor),
StringUtils.leftPad(convertSecondsToTime(measurementPeriod), measureLength));
}
// print summary line
formatter.format("%s\n", StringUtils.repeat("-", totalLength));
final int firstColWidth = arrivalLength + maxNameLength + colSep.length();
if (!arrivalRateOverflow)
{
formatter.format("%," + firstColWidth + "d" + colSep, arrivalRateSum);
}
else
{
formatter.format("%" + Math.max(firstColWidth, OVERFLOW_ERROR.length()) + "s" + colSep, OVERFLOW_ERROR);
}
if (!userOverflow)
{
formatter.format("%," + (maxUserLength) + "d" + colSep, numberOfUsersSum);
}
else
{
formatter.format("%" + Math.max((maxUserLength), OVERFLOW_ERROR.length()) + "s" + colSep, OVERFLOW_ERROR);
}
formatter.format("%" + (maxLoadFactor) + "s" + colSep, (sameLoadFactor) ? previousLoadFactor : " ");
formatter.format("%" + (measureLength) + "s\n", convertSecondsToTime(measurementPeriodMaximum));
formatter.close();
}
return configLine.toString();
}
/**
* Converts seconds to a more human readable time format. If the amount of seconds is negative, it will be first
* negated.
*
* @param seconds
* The number of seconds which should be converted.
* @return The returned time format follows the pattern h:mm:ss.
*/
public String convertSecondsToTime(long seconds)
{
// Alternative format: "%d h %02d min %02d sec"
seconds = Math.abs(seconds);
if (seconds > 0)
{
final long rem = seconds % 3600;
return String.format("%d:%02d:%02d", seconds / 3600, rem / 60, rem % 60);
}
return "0:00:00";
}
/**
* This method searches the lowest and highest value in a 2D-array and returns them as a pair.
*
* @param pairs
* 2D-array which follows the pattern {[timestamp_1, value_1], [timestamp_2, value_2] ...}
.
* @return Returns a pair, containing the lowest value in Left
and the highest value in
* Right
.
* If pairs is null
, null
will be returned as well.
*/
public Pair getMinMaxValue(int[][] pairs)
{
// Handle null objects. For example, arrivalRate and loadFactor might be null.
// Also make sure that the array contains at least one value (pairs[0][1]).
if (pairs == null || pairs.length == 0 || pairs[0].length == 1)
{
return null;
}
int min = pairs[0][1];
int max = pairs[0][1];
for (int i = 1; i < pairs.length; i++)
{
final int val = pairs[i][1];
min = Math.min(min, val);
max = Math.max(max, val);
}
return new ImmutablePair(min, max);
}
/**
* Calculates and stores the maximum String size for test case names, arrival rate, number of users and load factor.
* Numerical values will be first converted to a String representation before the maximum String length is
* determined. Numerical ranges will be also converted into String representations following the pattern
* minValue..maxValue.
* The maximum lengths will be stored in a map and can be accessed by referencing to the keys testCases,
* arrivalRate, users and loadFactor.
*
* @param loadTestSettings
* The configured load test settings that should be analyzed.
* @return A map which contains the String size of the longest test case name, arrival rate, number of users and
* load factor.
*/
private Map getColumnBoundaries(final TestLoadProfileConfiguration loadTestSettings)
{
int maxTestCaseLength = 0;
int maxArrivalRateLength = 0;
int maxUsersLength = 0;
int maxLoadFactor = 0;
final Map map = new HashMap<>();
for (final TestCaseLoadProfileConfiguration profile : loadTestSettings.getLoadTestConfiguration())
{
maxTestCaseLength = Math.max(maxTestCaseLength, profile.getUserName().length());
maxArrivalRateLength = Math.max(maxArrivalRateLength, getIntRangeAsString(getMinMaxValue(profile.getArrivalRate())).length());
maxUsersLength = Math.max(maxUsersLength, getIntRangeAsString(getMinMaxValue(profile.getNumberOfUsers())).length());
final Pair loadFactorPair = getMinMaxValue(profile.getLoadFactor());
if (loadFactorPair != null)
{
double min = loadFactorPair.getLeft() / 1000.0;
double max = loadFactorPair.getRight() / 1000.0;
maxLoadFactor = Math.max(maxLoadFactor, getDoubleRangeAsString(new ImmutablePair(min, max)).length());
}
else
{
maxLoadFactor = Math.max(maxLoadFactor, getDoubleRangeAsString(null).length());
}
}
map.put("testCases", maxTestCaseLength);
map.put("arrivalRate", maxArrivalRateLength);
map.put("users", maxUsersLength);
map.put("loadFactor", maxLoadFactor);
return map;
}
/**
* Converts the passed pair of Integers to a String.
*
* @param pair
* The pair that should be converted to a String.
* @return Depending on the content, one of following Strings will be returned:
* pair(null
) --> {@value #SETTING_NOT_AVAILABLE}
* pair(n, n) ---> "n"
* pair(m, n) ---> "m..n"
*/
private String getIntRangeAsString(final Pair pair)
{
if (pair == null)
{
return SETTING_NOT_AVAILABLE;
}
final int min = pair.getLeft();
final int max = pair.getRight();
return (min == max) ? String.format("%,d", max) : String.format("%,d..%,d", min, max);
}
/**
* Converts the passed pair of Floats to a String. Floats will be displayed with a precision of 3.
*
* @param pair
* The pair that should be converted to a String.
* @return Depending on the content, one of following Strings will be returned:
* pair(null
) --> {@value #SETTING_NOT_AVAILABLE}
* pair(n, n) ---> "n"
* pair(m, n) ---> "m..n"
*/
private String getDoubleRangeAsString(final Pair pair)
{
if (pair == null)
{
return SETTING_NOT_AVAILABLE;
}
final double min = pair.getLeft();
final double max = pair.getRight();
return (min == max) ? String.format("%,.3f", max) : String.format("%,.3f..%,.3f", min, max);
}
/**
* Prints the status of all agents to the console in a tabular format.
*/
public void printXltInfo()
{
final ProductInformation info = ProductInformation.getProductInformation();
// get the "to" year for the copyright
final GregorianCalendar cal = new GregorianCalendar();
cal.setTime(info.getBuildDate());
final int year = cal.get(Calendar.YEAR);
// format the info
final String productInfo = info.getProductIdentifier();
final String copyright = String.format(COPYRIGHT, year, info.getVendorName());
final String licenseInfo = "XLT is Open Source and available under the Apache License 2.0.";
// put it all together
System.out.printf("\n%s\n%s\n%s\n\n", productInfo, copyright, licenseInfo);
}
/**
* Prints some basic agent controller precheck information like accessibility or XLT conflict.
*/
public void printAgentControllerPreCheckInformation()
{
System.out.print("\nChecking for agent controller reachability and XLT version conflicts ... ");
final AgentControllersInformation agentControllerInfos = masterController.getAgentControllerInformation();
final StringBuilder sb = new StringBuilder();
// check for errors
if (agentControllerInfos.hasErrors())
{
sb.append("\n\n");
sb.append("WARNING: At least one agent controller is unreachable.\n\n");
for (final AgentControllerInfo agentControllerInfo : agentControllerInfos.getAgentControllerInformation())
{
final Exception ex = agentControllerInfo.getException();
if (ex != null)
{
sb.append(" -> ").append(agentControllerInfo.getName()).append(": ")
.append(getUserFriendlyExceptionMessage(agentControllerInfo.getException())).append('\n');
}
}
}
// check for different XLT version
else if (agentControllerInfos.hasXltVersionConflict())
{
sb.append("\n\n");
sb.append("WARNING: Master controller and agent controllers run different XLT versions.\n");
sb.append(" Master controller version: ")
.append(ProductInformation.getProductInformation().getCondensedProductIdentifier()).append('\n');
}
else
{
sb.append("OK\n");
}
System.out.println(sb.toString());
}
/**
* {@inheritDoc}
*/
@Override
public void receivingAgentStatus()
{
}
/**
* Runs the user interface.
*/
public abstract void run();
/**
* Sets whether to display detailed status information for each simulated test user, or whether status information
* will be aggregated into one line per user type.
*
* @param showDetailedStatusList
* whether to show detailed information
*/
public void setShowDetailedStatusList(final boolean showDetailedStatusList)
{
this.showDetailedStatusList = showDetailedStatusList;
}
/**
* Sets the number of seconds to wait before the status list is updated again.
*
* @param statusListUpdateInterval
* the update interval
*/
public void setStatusListUpdateInterval(final int statusListUpdateInterval)
{
this.statusListUpdateInterval = statusListUpdateInterval;
}
/**
* Triggers the master controller to start the agent on all known agent controllers.
*
* @param testCaseName
* the name of the test case to start the agents for, or null
if all active test cases
* should be started
* @param checkTestSuiteUploaded
* whether to check if the test suite was successfully uploaded before
* @return true
if the operation was successful for all agent controllers; false
otherwise
*/
public boolean startAgents(final String testCaseName, final boolean checkTestSuiteUploaded)
{
if (!checkTestSuiteUploaded || masterController.areAgentsInSync())
{
System.out.println("Starting agents... ");
boolean result = false;
try
{
result = masterController.startAgents(testCaseName);
}
catch (final AgentControllerException e)
{
print(e.getFailed());
}
catch (final Exception e)
{
print(e);
}
System.out.println();
return result;
}
else
{
System.out.println("The test suite has to be uploaded before a test can be started.\n");
return false;
}
}
/**
* {@inheritDoc}
*/
@Override
public void startingAgents()
{
System.out.print("Starting agents... ");
}
/**
* Triggers the master controller to stop the agent on all known agent controllers.
*
* @return true
if the operation was successful for all agent controllers; false
otherwise
*/
public boolean stopAgents()
{
System.out.println("Aborting agents... ");
boolean result = false;
try
{
result = masterController.stopAgents();
}
catch (final AgentControllerException e)
{
print(e.getFailed());
}
System.out.println();
return result;
}
/**
* {@inheritDoc}
*/
@Override
public void stoppingAgents()
{
System.out.print("Stopping agents... ");
}
/**
* {@inheritDoc}
*/
@Override
public void testResultsDownloaded(final FailedAgentControllerCollection results)
{
print(results);
}
/**
* Triggers the master controller to upload the agent files to all known agent controllers.
*
* @return true
if uploading was successful, false
otherwise
*/
public boolean uploadAgentFiles()
{
boolean result = false;
System.out.println("Uploading test suite... ");
try
{
masterController.updateAgentFiles();
result = true;
}
catch (final AgentControllerException e)
{
print(e.getFailed());
}
catch (final Exception t)
{
print(t);
}
System.out.println();
return result;
}
/**
* {@inheritDoc}
*/
@Override
public void uploadingAgentFiles()
{
System.out.print(" -> Uploading agent files... ");
}
/**
* {@inheritDoc}
*/
@Override
public void skipAgentControllerConnections(final FailedAgentControllerCollection unconnectedAgentControllers)
{
if (!unconnectedAgentControllers.isEmpty())
{
System.out.println("\n-> WARNING: Skipped unreachable agent controllers:");
for (final AgentController agentcontroller : unconnectedAgentControllers.getAgentControllers())
{
System.out.println(" SKIPPED " + agentcontroller);
}
}
}
/**
* Handles the test comment.
*/
protected void handleTestComment()
{
masterController.setTestComment4DownloadedResults();
}
/**
* Returns the given time (difference) value in the format "hours:mins:secs". Negative values are prefixed with a
* negative sign. The number of hours is not limited to 24.
*
* @param time
* the time to convert
* @return the formatted time
*/
private String formatTime(long time)
{
String sign = "";
if (time < 0)
{
sign = "-";
time = -time;
}
final long timeInSecs = time / 1000;
final long timeInMins = timeInSecs / 60;
final long timeInHours = timeInMins / 60;
final long secs = timeInSecs % 60;
final long mins = timeInMins % 60;
final long hours = timeInHours;
return String.format("%s%d:%02d:%02d", sign, hours, mins, secs);
}
/**
* Builds the list of failures ready for printing.
*
* @param results
* the list of errors
* @return the formatted failures
*/
private String buildFailureList(final FailedAgentControllerCollection results)
{
final StringBuilder failed = new StringBuilder();
for (final Map.Entry result : results.getMap().entrySet())
{
failed.append(" -> " + result.getKey());
final Exception ex = result.getValue();
if (ex != null)
{
final String msg = getUserFriendlyExceptionMessage(ex);
failed.append(" - " + msg);
}
failed.append("\n");
}
return failed.toString();
}
/**
* Returns a more user-friendly message for the given exception.
*
* @param ex
* the exception
* @return the user-friendly message
*/
protected String getUserFriendlyExceptionMessage(final Exception ex)
{
String msg = StringUtils.defaultString(ex.getMessage());
if (msg.startsWith("401:"))
{
msg = "Authentication failed. The agent controller rejected the master controller's password.";
}
return msg;
}
/**
* Prints the result of an operation at the given agent controller. If the operation was successful, the passed
* exception is null, otherwise it may hold more information.
*
* @param ex
* the exception
*/
private void print(final FailedAgentControllerCollection results)
{
final StringBuilder failed = new StringBuilder();
if (results != null && results.getMap().size() > 0)
{
failed.append("- FAILED!\n\n");
failed.append(buildFailureList(results));
System.out.println(failed);
}
else
{
System.out.println("- OK");
}
}
private void print(final Exception e)
{
System.out.println("- FAILED: " + e.getMessage());
}
private void print()
{
print((FailedAgentControllerCollection) null);
}
protected boolean checkAlive()
{
// check if agent controllers are alive.
// if we can not connect to an agent controller it has a running test potentially
try
{
masterController.checkAlive();
return true;
}
catch (final AgentControllerException e)
{
final StringBuilder sb = new StringBuilder();
sb.append("WARNING: Unable to execute this command because at least one agent controller is unreachable:\n\n");
sb.append(buildFailureList(e.getFailed()));
System.out.println(sb);
}
catch (final IllegalStateException e)
{
System.out.println("WARNING: Unable to execute this command: " + e.getMessage());
}
return false;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy