/**
* Copyright 2014 SAP AG
*
* 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 org.spotter.core;
import java.text.DecimalFormat;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import org.lpe.common.config.GlobalConfiguration;
import org.lpe.common.util.LpeStringUtils;
import org.lpe.common.util.system.LpeSystemUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spotter.core.detection.IDetectionController;
import org.spotter.shared.configuration.ConfigKeys;
import org.spotter.shared.status.DiagnosisProgress;
import org.spotter.shared.status.DiagnosisStatus;
import org.spotter.shared.status.SpotterProgress;
/**
* The ProgressUpdater periodically updates the progress of the detection
* controller in action.
*
* @author Alexander Wert
*
*/
public final class ProgressManager implements Runnable {
private static final double _100_PERCENT = 100.0;
private static final Logger LOGGER = LoggerFactory.getLogger(ProgressManager.class);
private static final int SECOND = 1000;
private static final int MIN_NUM_USERS = 1;
private static final double EPSILON = 0.5;
private static ProgressManager instance;
/**
* Get singleton instance.
*
* @return singleton instance
*/
public static synchronized ProgressManager getInstance() {
if (instance == null) {
instance = new ProgressManager();
}
return instance;
}
private volatile boolean run = false;
private IDetectionController controller;
private long estimatedDuration = 0;
private long additionalDuration = 0;
private long problemInvestigationStartedTimestamp;
private int samplingDelay = SECOND; // in [ms]
private Future<?> managingTask;
private SpotterProgress spotterProgress;
private boolean initialEstimateConducted = false;
private ProgressManager() {
spotterProgress = new SpotterProgress();
}
@Override
public void run() {
run = true;
while (run) {
synchronized (this) {
if (controller != null) {
if (!initialEstimateConducted) {
calculateInitialEstimatedDuration();
}
updateEstimatedProgress();
}
}
try {
Thread.sleep(samplingDelay);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
/**
* Starts execution of the updater.
*/
public synchronized void start() {
managingTask = LpeSystemUtils.submitTask(this);
}
/**
* Stops execution of the updater.
*/
public synchronized void stop() {
run = false;
if (managingTask != null) {
try {
managingTask.get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
}
}
/**
* Sets current detection controller.
*
* @param controller
* controller in action
*/
public synchronized void setController(IDetectionController controller) {
this.controller = controller;
estimatedDuration = 0;
additionalDuration = 0;
initialEstimateConducted = false;
String currentProblem = controller == null ? null : controller.getProblemId();
getSpotterProgress().setCurrentProblem(currentProblem);
}
/**
* Adds additional duration to the estimated duration.
*
* @param additionalDuration
* time in [s] to add
*/
public void addAdditionalDuration(long additionalDuration) {
this.additionalDuration += additionalDuration;
}
/**
* @return the estimatedOverallDuration
*/
public long getEstimatedOverallDuration() {
return estimatedDuration + additionalDuration;
}
private void calculateInitialEstimatedDuration() {
estimatedDuration = controller.getExperimentSeriesDuration();
problemInvestigationStartedTimestamp = System.currentTimeMillis();
initialEstimateConducted = true;
}
/**
* Calculates the duration of a single experiment (including warm-up and
* cool-down phases).
*
* @param numUsers
* number of users to ramp up
* @param stablePhaseDuration
* duration of the stable experimentation phase
* @return calculated duration
*/
public long calculateExperimentDuration(long numUsers, long stablePhaseDuration) {
long rampUpUsersPerInterval = GlobalConfiguration.getInstance().getPropertyAsLong(
ConfigKeys.EXPERIMENT_RAMP_UP_NUM_USERS_PER_INTERVAL, 0L);
long coolDownUsersPerInterval = GlobalConfiguration.getInstance().getPropertyAsLong(
ConfigKeys.EXPERIMENT_COOL_DOWN_NUM_USERS_PER_INTERVAL, 0L);
long rampUpInterval = GlobalConfiguration.getInstance().getPropertyAsLong(
ConfigKeys.EXPERIMENT_RAMP_UP_INTERVAL_LENGTH, 0L);
long coolDownInterval = GlobalConfiguration.getInstance().getPropertyAsLong(
ConfigKeys.EXPERIMENT_COOL_DOWN_INTERVAL_LENGTH, 0L);
long rampUp = 0;
if (rampUpUsersPerInterval != 0) {
rampUp = (numUsers / rampUpUsersPerInterval) * rampUpInterval;
}
long coolDown = 0;
if (coolDownUsersPerInterval != 0) {
coolDown = (numUsers / coolDownUsersPerInterval) * coolDownInterval;
}
return rampUp + stablePhaseDuration + coolDown;
}
/**
* Calculates the experiment series duration for a default experiment series
* with the given amount of experimentation steps.
*
* @param experimentSteps
* number of experimentation steps
* @return duration of a default experiment series
*/
public long calculateDefaultExperimentSeriesDuration(int experimentSteps) {
int maxUsers = Integer.parseInt(LpeStringUtils.getPropertyOrFail(GlobalConfiguration.getInstance()
.getProperties(), ConfigKeys.WORKLOAD_MAXUSERS, null));
if (experimentSteps <= 1) {
return calculateExperimentDuration(maxUsers,
GlobalConfiguration.getInstance().getPropertyAsLong(ConfigKeys.EXPERIMENT_DURATION, 0L));
} else {
double dMinUsers = MIN_NUM_USERS;
double dMaxUsers = maxUsers;
double dStep = (dMaxUsers - dMinUsers) / (double) (experimentSteps - 1);
// if we have the same number of maximum and minimum users, then we
// have only one experiment run
if (dStep <= 0.0 + EPSILON) {
return calculateExperimentDuration(MIN_NUM_USERS,
GlobalConfiguration.getInstance().getPropertyAsLong(ConfigKeys.EXPERIMENT_DURATION, 0L));
} else {
long duration = 0L;
for (double dUsers = dMinUsers; dUsers <= (dMaxUsers + EPSILON); dUsers += dStep) {
int numUsers = new Double(dUsers).intValue();
duration += calculateExperimentDuration(numUsers, GlobalConfiguration.getInstance()
.getPropertyAsLong(ConfigKeys.EXPERIMENT_DURATION, 0L));
}
return duration;
}
}
}
/**
* Updates the current progress of this controller.
*/
public void updateEstimatedProgress() {
long elapsedTime = (System.currentTimeMillis() - problemInvestigationStartedTimestamp) / SECOND;
long currentEstimatedOverallDuration = getEstimatedOverallDuration();
// as the estimated overall duration might not have been calculated yet
// and return default
// value 0, it must be checked to be greater 0
if (currentEstimatedOverallDuration > 0) {
updateProgress(controller.getProblemId(), (double) elapsedTime / (double) currentEstimatedOverallDuration,
currentEstimatedOverallDuration - elapsedTime);
}
if (LOGGER.isInfoEnabled()) {
DecimalFormat dFormat = new DecimalFormat("#00.0");
LOGGER.info("Progress - " + controller.getProvider().getName() + " - {}% - remaining: {}s",
dFormat.format(((double) elapsedTime / (double) currentEstimatedOverallDuration) * _100_PERCENT),
currentEstimatedOverallDuration - elapsedTime);
}
}
/**
* Updates the progress.
*
* @param problemId
* problem unique id specifying the corresponding diagnosis step
* @param estimatedProgress
* estimated progress in percent
* @param estimatedRemainingDuration
* estimated remaining duration in seconds
*/
public void updateProgress(String problemId, double estimatedProgress, long estimatedRemainingDuration) {
if (getSpotterProgress().getProblemProgressMapping().containsKey(problemId)) {
getSpotterProgress().getProblemProgressMapping().get(problemId).setEstimatedProgress(estimatedProgress);
getSpotterProgress().getProblemProgressMapping().get(problemId)
.setEstimatedRemainingDuration(estimatedRemainingDuration);
}
}
/**
* Sets the name for the problem with the given id.
*
* @param problemId
* problem unique id specifying the corresponding diagnosis step
* @param problemName
* name to set
*/
public void setProblemName(String problemId, String problemName) {
if (getSpotterProgress().getProblemProgressMapping().containsKey(problemId)) {
getSpotterProgress().getProblemProgressMapping().get(problemId).setName(problemName);
} else {
DiagnosisProgress progress = new DiagnosisProgress(problemName, null, 0.0, 0L, "");
getSpotterProgress().getProblemProgressMapping().put(problemId, progress);
}
}
/**
* Updates the progress status.
*
* @param problemId
* problem unique id specifying the corresponding diagnosis step
* @param status
* new status
*/
public void updateProgressStatus(String problemId, DiagnosisStatus status) {
if (getSpotterProgress().getProblemProgressMapping().containsKey(problemId)) {
getSpotterProgress().getProblemProgressMapping().get(problemId).setStatus(status);
} else {
DiagnosisProgress progress = new DiagnosisProgress("", status, 0.0, 0L, "");
getSpotterProgress().getProblemProgressMapping().put(problemId, progress);
}
}
/**
* Update progress message.
*
* @param problemId
* problem unique id specifying the corresponding diagnosis step
* @param currentProgressMessage
* new progress message
*/
public void updateProgressMessage(String problemId, String currentProgressMessage) {
if (getSpotterProgress().getProblemProgressMapping().containsKey(problemId)) {
getSpotterProgress().getProblemProgressMapping().get(problemId)
.setCurrentProgressMessage(currentProgressMessage);
}
}
/**
* Updates the progress status.
*
* @param problemId
* problem unique id specifying the corresponding diagnosis step
* @param currentProgressMessage
* new progress message
* @param status
* new status
*/
public void updateProgressStatus(String problemId, DiagnosisStatus status, String currentProgressMessage) {
if (getSpotterProgress().getProblemProgressMapping().containsKey(problemId)) {
DiagnosisProgress progress = getSpotterProgress().getProblemProgressMapping().get(problemId);
progress.setStatus(status);
progress.setCurrentProgressMessage(currentProgressMessage);
} else {
DiagnosisProgress progress = new DiagnosisProgress("", status, 0.0, 0L, currentProgressMessage);
getSpotterProgress().getProblemProgressMapping().put(problemId, progress);
}
}
/**
* Returns the spotter job progress.
*
* @return SpotterProgress
*/
public SpotterProgress getSpotterProgress() {
return spotterProgress;
}
/**
* @param samplingDelay
* the samplingDelay to set in [ms]
*/
public void setSamplingDelay(int samplingDelay) {
this.samplingDelay = samplingDelay;
}
/**
* Resets all properties of the progress manager. If the progress manager is
* running, this methods stops the manager.
*/
public void reset() {
if (run) {
stop();
}
run = false;
setController(null);
estimatedDuration = 0;
additionalDuration = 0;
initialEstimateConducted = false;
spotterProgress = new SpotterProgress();
}
}