/*
 * Copyright (C) 2011 Google Inc.
 *
 * 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.google.caliper.worker;

import static java.util.concurrent.TimeUnit.NANOSECONDS;

import com.google.caliper.model.Measurement;
import com.google.caliper.model.Value;
import com.google.caliper.runner.InvalidBenchmarkException;
import com.google.caliper.runner.Running.Benchmark;
import com.google.caliper.runner.Running.BenchmarkMethod;
import com.google.caliper.util.ShortDuration;
import com.google.caliper.util.Util;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Ticker;
import com.google.common.collect.ImmutableSet;
import com.google.inject.Inject;

import java.lang.reflect.Method;
import java.util.Map;
import java.util.Random;

/**
 * A {@link Worker} base class for micro and pico benchmarks.
 */
public abstract class RuntimeWorker extends Worker {
  @VisibleForTesting static final int INITIAL_REPS = 100;

  protected final Random random;
  protected final Ticker ticker;
  protected final Options options;
  private long totalReps;
  private long totalNanos;
  private long nextReps;

  RuntimeWorker(Object benchmark, 
      Method method, Random random, Ticker ticker,
      Map<String, String> workerOptions) {
    super(benchmark, method);
    this.random = random;
    // TODO(gak): investigate whether or not we can use Stopwatch
    this.ticker = ticker;
    this.options = new Options(workerOptions);
  }
  
  @Override public void bootstrap() throws Exception {
    totalReps = INITIAL_REPS;
    totalNanos = invokeTimeMethod(INITIAL_REPS);
  }

  @Override public void preMeasure() throws Exception {
    nextReps = calculateTargetReps(totalReps, totalNanos, options.timingIntervalNanos,
        random.nextGaussian());
    if (options.gcBeforeEach) {
      Util.forceGc();
    }
  }
  
  @Override public Iterable<Measurement> measure() throws Exception {
    long nanos = invokeTimeMethod(nextReps);
    Measurement measurement = new Measurement.Builder()
        .description("runtime")
        .value(Value.create(nanos, "ns"))
        .weight(nextReps)
        .build();
    
    totalReps += nextReps;
    totalNanos += nanos;
    return ImmutableSet.of(measurement);
  }
  
  abstract long invokeTimeMethod(long reps) throws Exception;

  /**
   * Returns a random number of reps based on a normal distribution around the estimated number of
   * reps for the timing interval. The distribution used has a standard deviation of one fifth of
   * the estimated number of reps.
   */
  @VisibleForTesting static long calculateTargetReps(long reps, long nanos, long targetNanos,
      double gaussian) {
    double targetReps = (((double) reps) / nanos) * targetNanos;
    return Math.max(1L, Math.round((gaussian * (targetReps / 5)) + targetReps));
  }

  /**
   * A {@link Worker} for micro benchmarks.
   */
  public static final class Micro extends RuntimeWorker {
    @Inject Micro(@Benchmark Object benchmark, 
        @BenchmarkMethod Method method, Random random, Ticker ticker,
        @WorkerOptions Map<String, String> workerOptions) {
      super(benchmark, method, random, ticker, workerOptions);
    }

    @Override  long invokeTimeMethod(long reps) throws Exception {
      int intReps = (int) reps;
      if (reps != intReps) {
        throw new InvalidBenchmarkException("%s.%s takes an int for reps, "
            + "but requires a greater number to fill the given timing interval (%s). "
            + "If this is expected (the benchmarked code is very fast), use a long parameter."
            + "Otherwise, check your benchmark for errors.",
                benchmark.getClass(), benchmarkMethod.getName(),
                    ShortDuration.of(options.timingIntervalNanos, NANOSECONDS));
      }
      long before = ticker.read();
      benchmarkMethod.invoke(benchmark, intReps);
      return ticker.read() - before;
    }
  }

  /**
   * A {@link Worker} for pico benchmarks.
   */
  public static final class Pico extends RuntimeWorker {
    @Inject Pico(@Benchmark Object benchmark, 
        @BenchmarkMethod Method method, Random random, Ticker ticker,
        @WorkerOptions Map<String, String> workerOptions) {
      super(benchmark, method, random, ticker, workerOptions);
    }
    
    @Override long invokeTimeMethod(long reps) throws Exception {
      long before = ticker.read();
      benchmarkMethod.invoke(benchmark, reps);
      return ticker.read() - before;
    }
  }

  private static final class Options {
    long timingIntervalNanos;
    boolean gcBeforeEach;

    Options(Map<String, String> optionMap) {
      this.timingIntervalNanos = Long.parseLong(optionMap.get("timingIntervalNanos"));
      this.gcBeforeEach = Boolean.parseBoolean(optionMap.get("gcBeforeEach"));
    }
  }
}

@@ -3697,17 +3697,16 @@
 verride 
- 
 long inv

Delete an extra space. ------------- Created by MOE: http://code.google.com/p/moe-java MOE_MIGRATED_REVID=59575610