Package org.ngrinder.perftest.service

Source Code of org.ngrinder.perftest.service.PerfTestRunnable

/*
* 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.ngrinder.perftest.service;

import net.grinder.SingleConsole;
import net.grinder.SingleConsole.ConsoleShutdownListener;
import net.grinder.StopReason;
import net.grinder.common.GrinderProperties;
import net.grinder.console.model.ConsoleProperties;
import net.grinder.util.ListenerHelper;
import net.grinder.util.ListenerSupport;
import net.grinder.util.UnitUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.time.DateUtils;
import org.ngrinder.common.constant.ControllerConstants;
import org.ngrinder.extension.OnTestLifeCycleRunnable;
import org.ngrinder.extension.OnTestSamplingRunnable;
import org.ngrinder.infra.config.Config;
import org.ngrinder.infra.plugin.PluginManager;
import org.ngrinder.infra.schedule.ScheduledTaskService;
import org.ngrinder.model.PerfTest;
import org.ngrinder.model.Status;
import org.ngrinder.perftest.model.NullSingleConsole;
import org.ngrinder.perftest.service.samplinglistener.*;
import org.ngrinder.script.handler.ScriptHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.io.File;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

import static org.apache.commons.lang.ObjectUtils.defaultIfNull;
import static org.ngrinder.common.constant.ClusterConstants.PROP_CLUSTER_SAFE_DIST;
import static org.ngrinder.common.util.AccessUtils.getSafe;
import static org.ngrinder.model.Status.*;

/**
* {@link PerfTest} run scheduler.
* <p/>
* This class is responsible to execute/finish the performance test. The job is
* started from {@link #doStart()}  and {@link #doFinish()} method. These
* methods are scheduled by Spring Task.
*
* @author JunHo Yoon
* @since 3.0
*/
@Profile("production")
@Component
public class PerfTestRunnable implements ControllerConstants {

  private static final Logger LOG = LoggerFactory.getLogger(PerfTestRunnable.class);

  @SuppressWarnings("SpringJavaAutowiringInspection")
  @Autowired
  private PerfTestService perfTestService;

  @Autowired
  private ConsoleManager consoleManager;

  @Autowired
  private AgentManager agentManager;

  @Autowired
  private PluginManager pluginManager;

  @Autowired
  private Config config;

  @Autowired
  private ScheduledTaskService scheduledTaskService;

  private Runnable startRunnable;

  private Runnable finishRunnable;

  @PostConstruct
  public void init() {
    // Clean up db first.
    doFinish(true);

    this.startRunnable = new Runnable() {
      @Override
      public void run() {
        startPeriodically();
      }
    };
    scheduledTaskService.addFixedDelayedScheduledTask(startRunnable, PERFTEST_RUN_FREQUENCY_MILLISECONDS);
    this.finishRunnable = new Runnable() {
      @Override
      public void run() {
        finishPeriodically();
      }
    };
    scheduledTaskService.addFixedDelayedScheduledTask(finishRunnable, PERFTEST_RUN_FREQUENCY_MILLISECONDS);

  }

  @PreDestroy
  public void destroy() {
    scheduledTaskService.removeScheduledJob(this.startRunnable);
    scheduledTaskService.removeScheduledJob(this.finishRunnable);
  }

  /**
   * Scheduled method for test execution. This method dispatches the test
   * candidates and run one of them. This method is responsible until a test
   * is executed.
   */
  public void startPeriodically() {
    doStart();
  }

  void doStart() {
    if (config.hasNoMoreTestLock()) {
      return;
    }
    // Block if the count of testing exceed the limit
    if (!canExecuteMore()) {
      // LOG MORE
      List<PerfTest> currentlyRunningTests = perfTestService.getCurrentlyRunningTest();
      LOG.debug("Currently running test is {}. No more tests can not run.", currentlyRunningTests.size());
      return;
    }
    // Find out next ready perftest
    PerfTest runCandidate = getRunnablePerfTest();
    if (runCandidate == null) {
      return;
    }

    if (!isScheduledNow(runCandidate)) {
      // this test project is reserved,but it isn't yet going to run test
      // right now.
      return;
    }


    if (!hasEnoughFreeAgents(runCandidate)) {
      return;
    }

    doTest(runCandidate);
  }

  private PerfTest getRunnablePerfTest() {
    return perfTestService.getNextRunnablePerfTestPerfTestCandidate();
  }

  private boolean canExecuteMore() {
    return consoleManager.getConsoleInUse().size() < perfTestService.getMaximumConcurrentTestCount();
  }

  private boolean isScheduledNow(PerfTest test) {
    Date current = new Date();
    Date scheduledDate = DateUtils
        .truncate((Date) defaultIfNull(test.getScheduledTime(), current), Calendar.MINUTE);
    return current.after(scheduledDate);
  }


  /**
   * Check the free agent availability for the given {@link PerfTest}.
   *
   * @param test {@link PerfTest}
   * @return true if enough agents
   */
  protected boolean hasEnoughFreeAgents(PerfTest test) {
    int size = agentManager.getAllFreeApprovedAgentsForUser(test.getCreatedUser()).size();
    if (test.getAgentCount() != null && test.getAgentCount() > size) {
      perfTestService.markProgress(test, "The test is tried to execute but there is not enough free agents."
          + "\n- Current free agent count : " + size + "  / Requested : " + test.getAgentCount() + "\n");
      return false;
    }
    return true;
  }

  /**
   * Run the given test.
   * <p/>
   * If fails, it marks STOP_BY_ERROR in the given {@link PerfTest} status
   *
   * @param perfTest perftest instance;
   */
  public void doTest(final PerfTest perfTest) {
    SingleConsole singleConsole = null;
    try {
      singleConsole = startConsole(perfTest);
      ScriptHandler prepareDistribution = perfTestService.prepareDistribution(perfTest);
      GrinderProperties grinderProperties = perfTestService.getGrinderProperties(perfTest, prepareDistribution);
      startAgentsOn(perfTest, grinderProperties, checkCancellation(singleConsole));
      distributeFileOn(perfTest, checkCancellation(singleConsole));

      singleConsole.setReportPath(perfTestService.getReportFileDirectory(perfTest));
      runTestOn(perfTest, grinderProperties, checkCancellation(singleConsole));
    } catch (SingleConsoleCancellationException ex) {
      // In case of error, mark the occurs error on perftest.
      doCancel(perfTest, singleConsole);
      notifyFinish(perfTest, StopReason.CANCEL_BY_USER);
    } catch (Exception e) {
      // In case of error, mark the occurs error on perftest.
      LOG.error("Error while executing test: {} - {} ", perfTest.getTestIdentifier(), e.getMessage());
      LOG.debug("Stack Trace is : ", e);
      doTerminate(perfTest, singleConsole);
      notifyFinish(perfTest, StopReason.ERROR_WHILE_PREPARE);
    }
  }

  /**
   * Check the cancellation status on console.
   *
   * @param singleConsole console
   * @return true if cancellation is requested.
   */
  SingleConsole checkCancellation(SingleConsole singleConsole) {
    if (singleConsole.isCanceled()) {
      throw new SingleConsoleCancellationException("Single Console " + singleConsole.getConsolePort()
          + " is canceled");
    }
    return singleConsole;
  }

  /**
   * Start a console for given {@link PerfTest}.
   *
   * @param perfTest perftest
   * @return started console
   */
  SingleConsole startConsole(PerfTest perfTest) {
    perfTestService.markStatusAndProgress(perfTest, START_CONSOLE, "Console is being prepared.");
    // get available consoles.
    ConsoleProperties consoleProperty = perfTestService.createConsoleProperties(perfTest);
    SingleConsole singleConsole = consoleManager.getAvailableConsole(consoleProperty);
    singleConsole.start();
    perfTestService.markPerfTestConsoleStart(perfTest, singleConsole.getConsolePort());
    return singleConsole;
  }

  /**
   * Distribute files to agents.
   *
   * @param perfTest      perftest
   * @param singleConsole console to be used.
   */
  void distributeFileOn(final PerfTest perfTest, SingleConsole singleConsole) {
    // Distribute files
    perfTestService.markStatusAndProgress(perfTest, DISTRIBUTE_FILES, "All necessary files are being distributed.");
    ListenerSupport<SingleConsole.FileDistributionListener> listener = ListenerHelper.create();
    final long safeThreadHold = getSafeTransmissionThreshold();

    listener.add(new SingleConsole.FileDistributionListener() {
      @Override
      public void distributed(String fileName) {
        perfTestService.markProgress(perfTest, " - " + fileName);
      }

      @Override
      public boolean start(File dir, boolean safe) {
        if (safe) {
          perfTestService.markProgress(perfTest, "Safe file distribution mode is enabled.");
          return safe;
        }
        long sizeOfDirectory = FileUtils.sizeOfDirectory(dir);
        if (sizeOfDirectory > safeThreadHold) {
          perfTestService.markProgress(perfTest, "The total size of distributed files is over "
              + UnitUtils.byteCountToDisplaySize(safeThreadHold) + "B.\n- Safe file distribution mode is enabled by force.");
          return true;
        }
        return safe;
      }

    });

    // the files have prepared before
    singleConsole.distributeFiles(perfTestService.getDistributionPath(perfTest), listener,
        isSafeDistPerfTest(perfTest));
    perfTestService.markStatusAndProgress(perfTest, DISTRIBUTE_FILES_FINISHED,
        "All necessary files are distributed.");
  }

  protected long getSafeTransmissionThreshold() {
    return config.getControllerProperties().getPropertyLong(PROP_CONTROLLER_SAFE_DIST_THRESHOLD);
  }

  private boolean isSafeDistPerfTest(final PerfTest perfTest) {
    boolean safeDist = getSafe(perfTest.getSafeDistribution());
    if (config.isClustered()) {
      safeDist = config.getClusterProperties().getPropertyBoolean(PROP_CLUSTER_SAFE_DIST);
    }
    return safeDist;
  }

  /**
   * Start agents for the given {@link PerfTest}.
   *
   * @param perfTest          perftest
   * @param grinderProperties grinder properties
   * @param singleConsole     console to be used.
   */
  void startAgentsOn(PerfTest perfTest, GrinderProperties grinderProperties, SingleConsole singleConsole) {
    perfTestService.markStatusAndProgress(perfTest, START_AGENTS, getSafe(perfTest.getAgentCount())
        + " agents are starting.");
    agentManager.runAgent(perfTest.getCreatedUser(), singleConsole, grinderProperties,
        getSafe(perfTest.getAgentCount()));
    singleConsole.waitUntilAgentConnected(perfTest.getAgentCount());
    perfTestService.markStatusAndProgress(perfTest, START_AGENTS_FINISHED, getSafe(perfTest.getAgentCount())
        + " agents are ready.");
  }

  /**
   * Run a given {@link PerfTest} with the given {@link GrinderProperties} and
   * the {@link SingleConsole} .
   *
   * @param perfTest          perftest
   * @param grinderProperties grinder properties
   * @param singleConsole     console to be used.
   */
  void runTestOn(final PerfTest perfTest, GrinderProperties grinderProperties, final SingleConsole singleConsole) {
    // start target monitor
    for (OnTestLifeCycleRunnable run : pluginManager.getEnabledModulesByClass(OnTestLifeCycleRunnable.class)) {
      run.start(perfTest, perfTestService, config.getVersion());
    }

    // Run test
    perfTestService.markStatusAndProgress(perfTest, START_TESTING, "The test is ready to start.");
    // Add listener to detect abnormal condition and mark the perfTest
    singleConsole.addListener(new ConsoleShutdownListener() {
      @Override
      public void readyToStop(StopReason stopReason) {
        perfTestService.markAbnormalTermination(perfTest, stopReason);
        LOG.error("Abnormal test {} due to {}", perfTest.getId(), stopReason.name());
      }
    });
    long startTime = singleConsole.startTest(grinderProperties);
    perfTest.setStartTime(new Date(startTime));
    addSamplingListeners(perfTest, singleConsole);
    perfTestService.markStatusAndProgress(perfTest, TESTING, "The test is started.");
    singleConsole.startSampling();

  }

  protected void addSamplingListeners(final PerfTest perfTest, final SingleConsole singleConsole) {
    // Add SamplingLifeCycleListener
    singleConsole.addSamplingLifeCyleListener(new PerfTestSamplingCollectorListener(singleConsole,
        perfTest.getId(), perfTestService, scheduledTaskService));
    singleConsole.addSamplingLifeCyleListener(new AgentLostDetectionListener(singleConsole, perfTest,
        perfTestService, scheduledTaskService));
    List<OnTestSamplingRunnable> testSamplingPlugins = pluginManager.getEnabledModulesByClass
        (OnTestSamplingRunnable.class, new MonitorCollectorPlugin(config, scheduledTaskService,
            perfTestService, perfTest.getId()));
    singleConsole.addSamplingLifeCyleListener(new PluginRunListener(testSamplingPlugins, singleConsole,
        perfTest, perfTestService));
    singleConsole.addSamplingLifeCyleListener(new AgentDieHardListener(singleConsole, perfTest, perfTestService,
        agentManager, scheduledTaskService));
  }


  /**
   * Notify test finish to plugins.
   *
   * @param perfTest PerfTest
   * @param reason   the reason of test finish..
   * @see OnTestLifeCycleRunnable
   */
  public void notifyFinish(PerfTest perfTest, StopReason reason) {
    for (OnTestLifeCycleRunnable run : pluginManager.getEnabledModulesByClass(OnTestLifeCycleRunnable.class)) {
      run.finish(perfTest, reason.name(), perfTestService, config.getVersion());
    }
  }

  /**
   * Finish the tests.(Scheduled by SpringTask)
   * <p/>
   * There are three types of test finish.
   * <p/>
   * <ul>
   * <li>Abnormal test finish : when TPS is too low or too many errors occur</li>
   * <li>User requested test finish : when user requested to finish the test</li>
   * <li>Normal test finish : when the test reaches the planned duration and run
   * count.</li>
   * </ul>
   */
  public void finishPeriodically() {
    doFinish(false);
  }

  protected void doFinish(boolean initial) {
    if (!initial && consoleManager.getConsoleInUse().isEmpty()) {
      return;
    }
    doFinish();
  }

  void doFinish() {
    for (PerfTest each : perfTestService.getAllAbnormalTesting()) {
      LOG.info("Terminate {}", each.getId());
      SingleConsole consoleUsingPort = consoleManager.getConsoleUsingPort(each.getPort());
      doTerminate(each, consoleUsingPort);
      cleanUp(each);
      notifyFinish(each, StopReason.TOO_MANY_ERRORS);
    }

    for (PerfTest each : perfTestService.getAllStopRequested()) {
      LOG.info("Stop test {}", each.getId());
      SingleConsole consoleUsingPort = consoleManager.getConsoleUsingPort(each.getPort());
      doCancel(each, consoleUsingPort);
      cleanUp(each);
      notifyFinish(each, StopReason.CANCEL_BY_USER);
    }

    for (PerfTest each : perfTestService.getAllTesting()) {
      SingleConsole consoleUsingPort = consoleManager.getConsoleUsingPort(each.getPort());
      if (isTestFinishCandidate(each, consoleUsingPort)) {
        doNormalFinish(each, consoleUsingPort);
        cleanUp(each);
        notifyFinish(each, StopReason.NORMAL);
      }
    }
  }

  /**
   * Clean up distribution directory for the given perfTest.
   *
   * @param perfTest perfTest
   */
  private void cleanUp(PerfTest perfTest) {
    perfTestService.cleanUpDistFolder(perfTest);
    perfTestService.cleanUpRuntimeOnlyData(perfTest);
  }

  /**
   * Check if the given {@link PerfTest} is ready to finish.
   *
   * @param perfTest           perf test
   * @param singleConsoleInUse singleConsole
   * @return true if it's a finish candidate.
   */
  private boolean isTestFinishCandidate(PerfTest perfTest, SingleConsole singleConsoleInUse) {
    // Give 5 seconds to be finished
    if (perfTest.isThresholdDuration()
        && singleConsoleInUse.isCurrentRunningTimeOverDuration(perfTest.getDuration())) {
      LOG.debug(
          "Test {} is ready to finish. Current : {}, Planned : {}",
          new Object[]{perfTest.getTestIdentifier(), singleConsoleInUse.getCurrentRunningTime(),
              perfTest.getDuration()});
      return true;
    } else if (perfTest.isThresholdRunCount()
        && singleConsoleInUse.getCurrentExecutionCount() >= perfTest.getTotalRunCount()) {
      LOG.debug("Test {} is ready to finish. Current : {}, Planned : {}",
          new Object[]{perfTest.getTestIdentifier(), singleConsoleInUse.getCurrentExecutionCount(),
              perfTest.getTotalRunCount()});
      return true;
    } else if (singleConsoleInUse instanceof NullSingleConsole) {
      LOG.debug("Test {} is ready to finish. Current : {}, Planned : {}",
          new Object[]{perfTest.getTestIdentifier(), singleConsoleInUse.getCurrentExecutionCount(),
              perfTest.getTotalRunCount()});
      return true;
    }

    return false;
  }

  /**
   * Cancel the given {@link PerfTest}.
   *
   * @param perfTest           {@link PerfTest} to be canceled.
   * @param singleConsoleInUse {@link SingleConsole} which is being used for the given
   *                           {@link PerfTest}
   */
  public void doCancel(PerfTest perfTest, SingleConsole singleConsoleInUse) {
    LOG.info("Cancel test {} by user request.", perfTest.getId());
    singleConsoleInUse.unregisterSampling();
    try {
      perfTestService.markProgressAndStatusAndFinishTimeAndStatistics(perfTest, CANCELED,
          "Stop requested by user");
    } catch (Exception e) {
      LOG.error("Error while canceling test {} : {}", perfTest.getId(), e.getMessage());
      LOG.debug("Details : ", e);
    }
    consoleManager.returnBackConsole(perfTest.getTestIdentifier(), singleConsoleInUse);
  }

  /**
   * Terminate the given {@link PerfTest}.
   *
   * @param perfTest           {@link PerfTest} to be finished
   * @param singleConsoleInUse {@link SingleConsole} which is being used for the given
   *                           {@link PerfTest}
   */
  public void doTerminate(PerfTest perfTest, SingleConsole singleConsoleInUse) {
    singleConsoleInUse.unregisterSampling();
    try {
      perfTestService.markProgressAndStatusAndFinishTimeAndStatistics(perfTest, Status.STOP_BY_ERROR,
          "Stopped by error");
    } catch (Exception e) {
      LOG.error("Error while terminating {} : {}", perfTest.getTestIdentifier(), e.getMessage());
      LOG.debug("Details : ", e);
    }
    consoleManager.returnBackConsole(perfTest.getTestIdentifier(), singleConsoleInUse);
  }

  /**
   * Finish the given {@link PerfTest}.
   *
   * @param perfTest           {@link PerfTest} to be finished
   * @param singleConsoleInUse {@link SingleConsole} which is being used for the given
   *                           {@link PerfTest}
   */
  public void doNormalFinish(PerfTest perfTest, SingleConsole singleConsoleInUse) {
    LOG.debug("PerfTest {} status - currentRunningTime {} ", perfTest.getId(),
        singleConsoleInUse.getCurrentRunningTime());
    singleConsoleInUse.unregisterSampling();
    try {
      // stop target host monitor
      if (perfTestService.hasTooManyError(perfTest)) {
        perfTestService.markProgressAndStatusAndFinishTimeAndStatistics(perfTest, Status.STOP_BY_ERROR,
            "[WARNING] The test is finished but contains too much errors(over 30% of total runs).");
      } else if (singleConsoleInUse.hasNoPerformedTest()) {
        perfTestService.markProgressAndStatusAndFinishTimeAndStatistics(perfTest, Status.STOP_BY_ERROR,
            "[WARNING] The test is finished but has no TPS.");
      } else {
        perfTestService.markProgressAndStatusAndFinishTimeAndStatistics(perfTest, Status.FINISHED,
            "The test is successfully finished.");
      }
    } catch (Exception e) {
      perfTestService.markStatusAndProgress(perfTest, Status.STOP_BY_ERROR, e.getMessage());
      LOG.error("Error while finishing {} : {}", perfTest.getTestIdentifier(), e.getMessage());
      LOG.debug("Details : ", e);
    }
    consoleManager.returnBackConsole(perfTest.getTestIdentifier(), singleConsoleInUse);
  }

  public PerfTestService getPerfTestService() {
    return perfTestService;
  }

  public AgentManager getAgentManager() {
    return agentManager;
  }

}
TOP

Related Classes of org.ngrinder.perftest.service.PerfTestRunnable

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.