/**
   * Retrieves the value associated with the provided iteration of the given optimizing job.
   *
   * @param iteration The job iteration for which to retrieve the value.
   * @return The value associated with the provided iteration of the given optimizing job.
   * @throws SLAMDException If a problem occurs while trying to determine the value for the given
   *     optimizing job iteration.
   */
  @Override()
  public double getIterationOptimizationValue(Job iteration) throws SLAMDException {
    StatTracker[] trackers = iteration.getStatTrackers(optimizeStat);
    if ((trackers == null) || (trackers.length == 0)) {
      throw new SLAMDException(
          "The provided optimizing job iteration did "
              + "not include any values for the statistic to "
              + "optimize, \""
              + optimizeStat
              + "\".");
    }

    StatTracker tracker = trackers[0].newInstance();
    tracker.aggregate(trackers);

    double summaryValue = tracker.getSummaryValue();
    iteration.slamdServer.logMessage(
        Constants.LOG_LEVEL_JOB_DEBUG,
        "SingleStatisticWithReplicationLatency"
            + "OptimizationAlgorithm."
            + "getIterationOptimizationValue("
            + iteration.getJobID()
            + ") returning "
            + summaryValue);

    return summaryValue;
  }
  /**
   * Indicates whether the provided job iteration has an acceptable CPU utilization.
   *
   * @param iteration The iteration for which to make the determination.
   * @return <CODE>true</CODE> if the CPU utilization for the provided iteration is acceptable, or
   *     <CODE>false</CODE> if not.
   * @throws SLAMDException If the provided iteration does not include sufficient CPU utilization
   *     data to make the determination.
   */
  private boolean isAcceptableReplicationLatency(Job iteration) throws SLAMDException {
    boolean latencyFound = false;

    String className = ReplicationLatencyResourceMonitor.class.getName();
    ResourceMonitorStatTracker[] monitorTrackers =
        iteration.getResourceMonitorStatTrackersForClass(className);

    for (int i = 0; i < monitorTrackers.length; i++) {
      StatTracker tracker = monitorTrackers[i].getStatTracker();
      String name = tracker.getDisplayName();

      if ((tracker instanceof TimeTracker)
          && name.endsWith(ReplicationLatencyResourceMonitor.STAT_TRACKER_REPLICATION_LATENCY)) {
        latencyFound = true;

        if (maxLatency > 0) {
          TimeTracker latencyTimer = (TimeTracker) tracker;
          double averageLatency = latencyTimer.getAverageDuration();
          if (averageLatency > maxLatency) {
            return false;
          }

          int[] intervalDurations = latencyTimer.getIntervalDurations();
          int[] intervalCounts = latencyTimer.getIntervalCounts();
          int numIntervals = intervalCounts.length;
          int quarterOfIntervals = numIntervals / 4;

          int firstTotal = 0;
          int firstCount = 0;
          int lastTotal = 0;
          int lastCount = 0;
          for (int j = 0; j < quarterOfIntervals; j++) {
            firstTotal += intervalDurations[j];
            firstCount += intervalCounts[j];
            lastTotal += intervalDurations[numIntervals - quarterOfIntervals + j];
            lastCount += intervalCounts[numIntervals - quarterOfIntervals + j];
          }

          if ((firstCount == 0) || (lastCount == 0)) {
            return false;
          }

          double firstAvg = 1.0 * firstTotal / firstCount;
          double lastAvg = 1.0 * lastTotal / lastCount;
          if (lastAvg > firstAvg) {
            double pctIncrease = (lastAvg - firstAvg) / firstAvg * 100.0;
            if (pctIncrease > maxIncrease) {
              return false;
            }
          }
        }
      }
    }

    if (!latencyFound) {
      throw new SLAMDException(
          "The provided job iteration did not include " + "any replication latency data.");
    }

    return true;
  }
  /**
   * Initializes this optimization algorithm with the provided set of parameters for the given
   * optimizing job.
   *
   * @param optimizingJob The optimizing job with which this optimization algorithm will be used.
   * @param parameters The parameter list containing the parameter values provided by the end user
   *     when scheduling the optimizing job.
   * @throws InvalidValueException If the contents of the provided parameter list are not valid for
   *     use with this optimization algorithm.
   */
  @Override()
  public void initializeOptimizationAlgorithm(OptimizingJob optimizingJob, ParameterList parameters)
      throws InvalidValueException {
    this.optimizingJob = optimizingJob;

    String[] monitorClients = optimizingJob.getResourceMonitorClients();
    if ((monitorClients == null) || (monitorClients.length == 0)) {
      throw new InvalidValueException(
          "No resource monitor clients have been "
              + "requested for this optimizing job.  "
              + "At least one is required to provide "
              + "replication latency data.");
    }

    // Get the optimization statistic parameter and name.
    optimizeStatParameter = parameters.getMultiChoiceParameter(PARAM_OPTIMIZE_STAT);
    if ((optimizeStatParameter == null) || (!optimizeStatParameter.hasValue())) {
      throw new InvalidValueException("No value provided for the statistic " + "to optimize");
    }
    optimizeStat = optimizeStatParameter.getStringValue();

    // Get the optimization type parameter and value.
    optimizeTypeParameter = parameters.getMultiChoiceParameter(PARAM_OPTIMIZE_TYPE);
    if ((optimizeTypeParameter == null) || (!optimizeTypeParameter.hasValue())) {
      throw new InvalidValueException("No value provided for the " + "optimization type");
    }
    String optimizeTypeStr = optimizeTypeParameter.getStringValue();
    if (optimizeTypeStr.equalsIgnoreCase(Constants.OPTIMIZE_TYPE_MAXIMIZE)) {
      optimizeType = OPTIMIZE_TYPE_MAXIMIZE;
    } else if (optimizeTypeStr.equalsIgnoreCase(Constants.OPTIMIZE_TYPE_MINIMIZE)) {
      optimizeType = OPTIMIZE_TYPE_MINIMIZE;
    } else {
      throw new InvalidValueException(
          "Invalid value \"" + optimizeTypeStr + "\" for optimization type.");
    }

    // Get the maximum percent increase parameter and value.
    maxIncreaseParameter = parameters.getFloatParameter(PARAM_MAX_PERCENT_INCREASE);
    if ((maxIncreaseParameter == null) || (!maxIncreaseParameter.hasValue())) {
      throw new InvalidValueException(
          "No value provided for the maximum "
              + "allowed percentage increase in "
              + "replication latency.");
    }
    maxIncrease = maxIncreaseParameter.getFloatValue();

    // Get the maximum latency parameter and value.
    maxLatencyParameter = parameters.getFloatParameter(PARAM_MAX_REPLICA_LATENCY);
    if ((maxLatencyParameter == null) || (!maxLatencyParameter.hasValue())) {
      maxLatency = -1.0;
    } else {
      maxLatency = maxLatencyParameter.getFloatValue();
    }

    // Get the minimum percent improvement required for a new best iteration.
    minPctImprovement = 0.0F;
    minPctImprovementParameter = parameters.getFloatParameter(PARAM_MIN_PCT_IMPROVEMENT);
    if ((minPctImprovementParameter != null) && minPctImprovementParameter.hasValue()) {
      minPctImprovement = minPctImprovementParameter.getFloatValue();
    }

    // See If the provided optimizing job has run any iterations so far.  If so,
    // then look through them to determine the best value so far.
    bestValueSoFar = Double.NaN;
    Job[] iterations = optimizingJob.getAssociatedJobs();
    if (iterations != null) {
      for (int i = 0; i < iterations.length; i++) {
        try {
          if (!isAcceptableReplicationLatency(iterations[i])) {
            continue;
          }
        } catch (Exception e) {
        }

        StatTracker[] trackers = iterations[i].getStatTrackers(optimizeStat);
        if ((trackers != null) && (trackers.length > 0)) {
          StatTracker tracker = trackers[0].newInstance();
          tracker.aggregate(trackers);
          double value = tracker.getSummaryValue();
          if (Double.isNaN(bestValueSoFar)) {
            bestValueSoFar = value;
          } else if ((optimizeType == OPTIMIZE_TYPE_MAXIMIZE)
              && (value > bestValueSoFar)
              && (value >= (bestValueSoFar + bestValueSoFar * minPctImprovement))) {
            bestValueSoFar = value;
          } else if ((optimizeType == OPTIMIZE_TYPE_MINIMIZE)
              && (value < bestValueSoFar)
              && (value <= (bestValueSoFar - bestValueSoFar * minPctImprovement))) {
            bestValueSoFar = value;
          }
        }
      }
    }

    SLAMDServer slamdServer = optimizingJob.slamdServer;
    slamdServer.logMessage(
        Constants.LOG_LEVEL_JOB_DEBUG,
        "SingleStatisticWithReplicationLatencyOptimization"
            + "Algorithm.initializeOptimizationAlgorith("
            + optimizingJob.getOptimizingJobID()
            + ") best so far is "
            + String.valueOf(bestValueSoFar));
  }
  /**
   * Indicates whether the provided job iteration has an acceptable CPU utilization.
   *
   * @param iteration The iteration for which to make the determination.
   * @return <CODE>true</CODE> if the CPU utilization for the provided iteration is acceptable, or
   *     <CODE>false</CODE> if not.
   * @throws SLAMDException If the provided iteration does not include sufficient CPU utilization
   *     data to make the determination.
   */
  private boolean isAcceptableCPUUtilization(Job iteration) throws SLAMDException {
    SLAMDServer slamdServer = iteration.slamdServer;
    boolean utilizationFound = false;

    String className = VMStatResourceMonitor.class.getName();
    ResourceMonitorStatTracker[] monitorTrackers =
        iteration.getResourceMonitorStatTrackersForClass(className);
    for (int i = 0; i < monitorTrackers.length; i++) {
      StatTracker tracker = monitorTrackers[i].getStatTracker();
      String name = tracker.getDisplayName();

      if ((tracker instanceof StackedValueTracker)
          && name.endsWith(VMStatResourceMonitor.STAT_TRACKER_CPU_UTILIZATION)) {
        utilizationFound = true;
        StackedValueTracker utilizationTracker = (StackedValueTracker) tracker;
        double userTime =
            utilizationTracker.getAverageValue(VMStatResourceMonitor.UTILIZATION_CATEGORY_USER);
        double systemTime =
            utilizationTracker.getAverageValue(VMStatResourceMonitor.UTILIZATION_CATEGORY_SYSTEM);
        double busyTime = userTime + systemTime;

        switch (utilizationComponent) {
          case UTILIZATION_COMPONENT_USER_TIME:
            if (userTime > maxUtilization) {
              slamdServer.logMessage(
                  Constants.LOG_LEVEL_JOB_DEBUG,
                  "SingleStatisticWithCPUUtilization"
                      + "OptimizationAlgorithm.isAcceptableCPU"
                      + "Utilization("
                      + iteration.getJobID()
                      + ") returning false because user time of "
                      + userTime
                      + " for stat "
                      + tracker.getDisplayName()
                      + " exceeded the maximum allowed of "
                      + maxUtilization);
              return false;
            }
            break;
          case UTILIZATION_COMPONENT_SYSTEM_TIME:
            if (systemTime > maxUtilization) {
              slamdServer.logMessage(
                  Constants.LOG_LEVEL_JOB_DEBUG,
                  "SingleStatisticWithCPUUtilization"
                      + "OptimizationAlgorithm.isAcceptableCPU"
                      + "Utilization("
                      + iteration.getJobID()
                      + ") returning false because system time "
                      + "of "
                      + systemTime
                      + " for stat "
                      + tracker.getDisplayName()
                      + " exceeded the maximum allowed of "
                      + maxUtilization);
              return false;
            }
            break;
          case UTILIZATION_COMPONENT_BUSY_TIME:
            if (busyTime > maxUtilization) {
              slamdServer.logMessage(
                  Constants.LOG_LEVEL_JOB_DEBUG,
                  "SingleStatisticWithCPUUtilization"
                      + "OptimizationAlgorithm.isAcceptableCPU"
                      + "Utilization("
                      + iteration.getJobID()
                      + ") returning false because busy time of "
                      + busyTime
                      + " for stat "
                      + tracker.getDisplayName()
                      + " exceeded the maximum allowed of "
                      + maxUtilization);
              return false;
            }
            break;
          default:
            slamdServer.logMessage(
                Constants.LOG_LEVEL_JOB_DEBUG,
                "SingleStatisticWithCPUUtilization"
                    + "OptimizationAlgorithm.isAcceptableCPU"
                    + "Utilization("
                    + iteration.getJobID()
                    + ") returning false because an unknown "
                    + "utilization component of "
                    + utilizationComponent
                    + " is in use.");
            return false;
        }
      } else if ((tracker instanceof IntegerValueTracker)
              && ((utilizationComponent == UTILIZATION_COMPONENT_USER_TIME)
                  && name.endsWith(VMStatResourceMonitor.STAT_TRACKER_CPU_USER))
          || ((utilizationComponent == UTILIZATION_COMPONENT_SYSTEM_TIME)
              && name.endsWith(VMStatResourceMonitor.STAT_TRACKER_CPU_SYSTEM))
          || ((utilizationComponent == UTILIZATION_COMPONENT_BUSY_TIME)
              && name.endsWith(VMStatResourceMonitor.STAT_TRACKER_CPU_BUSY))) {
        utilizationFound = true;
        double value = ((IntegerValueTracker) tracker).getAverageValue();
        if (value > maxUtilization) {
          slamdServer.logMessage(
              Constants.LOG_LEVEL_JOB_DEBUG,
              "SingleStatisticWithCPUUtilization"
                  + "OptimizationAlgorithm.isAcceptableCPU"
                  + "Utilization("
                  + iteration.getJobID()
                  + ") returning false because value of "
                  + value
                  + " for stat "
                  + tracker.getDisplayName()
                  + " exceeded the maximum allowed of "
                  + maxUtilization);
          return false;
        }
      }
    }

    if (!utilizationFound) {
      throw new SLAMDException(
          "The provided job iteration did not include " + "any CPU utilization data.");
    }

    slamdServer.logMessage(
        Constants.LOG_LEVEL_JOB_DEBUG,
        "SingleStatisticWithCPUUtilizationOptimization"
            + "Algorithm.isAcceptableCPUUtilization("
            + iteration.getJobID()
            + ") returning true.");
    return true;
  }