/*
 * The Alluxio Open Foundation licenses this work under the Apache License, version 2.0
 * (the "License"). You may not use this work except in compliance with the License, which is
 * available at www.apache.org/licenses/LICENSE-2.0
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied, as more fully set forth in the License.
 *
 * See the NOTICE file distributed with this work for information regarding copyright ownership.
 */

package alluxio.inode;

import static alluxio.inode.RocksBenchBase.NO_SER_NO_ALLOC_READ;
import static alluxio.inode.RocksBenchBase.NO_SER_READ;
import static alluxio.inode.RocksBenchBase.SER_NO_ALLOC_READ;
import static alluxio.inode.RocksBenchBase.SER_READ;
import static alluxio.inode.RocksBenchBase.genInode;

import alluxio.BaseFileStructure;
import alluxio.BaseThreadState;
import alluxio.master.file.meta.MutableInode;

import org.junit.Assert;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.Level;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.TearDown;
import org.openjdk.jmh.infra.Blackhole;
import org.openjdk.jmh.profile.StackProfiler;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;

import java.io.IOException;

/**
 * This benchmark measures the performance of RocksDB single key accesses.
 * The keys are InodeIDs and the values are the Inode themselves.
 * The following options can be varied.
 * mUserSerialization - enable or disable ProtoBuf serialization of values.
 * mFileCount - the initial number of inodes stored in RocksDB.
 * mIsDirectory - have the Inodes represent directories or files.
 * mUseZipf - if to use a Zipfian distribution when choosing the keys to read.
 *  The more likely keys to be read will be the ones written last,
 *  meaning that they will more likely be in the RocksDB memtable.
 * mRocksConfig - see {@link RocksBenchConfig}
 */
public class RocksBenchRead {
  @State(Scope.Thread)
  public static class ThreadState extends BaseThreadState {
    byte[] mInodeRead;

    @Setup(Level.Trial)
    public void setup() {
      mInodeRead = new byte[1024];
    }
  }

  @State(Scope.Benchmark)
  public static class Db extends BaseFileStructure {
    @Param({"0"})
    public int mWidth;

    @Param({"1000"})
    public int mFileCount;

    // is used in read benchmark to simulate different file access patterns
    @Param({"ZIPF"})
    public Distribution mDistribution;
    @Param({SER_READ})
    public String mReadType;

    @Param({"false"})
    public boolean mIsDirectory;

    @Param({RocksBenchConfig.JAVA_CONFIG})
    public String mRocksConfig;

    RocksBenchBase mBase;

    @Setup(Level.Trial)
    public void setup() throws IOException {
      Assert.assertTrue("mFileCount must be > 0", mFileCount > 0);
      super.init(0, mWidth, mFileCount, mDistribution);

      MutableInode<?> inode = genInode(mIsDirectory);
      mBase = new RocksBenchBase(mRocksConfig);
      for (long i = 0; i < mFileCount; i++) {
        mBase.writeInode(i, 1, 0, inode);
      }
    }

    @TearDown(Level.Trial)
    public void after() {
      mBase.after();
      mBase = null;
    }
  }

  @Benchmark
  public void testMethod(Db db, ThreadState ts, Blackhole bh) {
    long id = ts.nextFileId(db, 0);
    switch (db.mReadType) {
      case SER_READ:
        bh.consume(db.mBase.readInode(db.mFileCount - 1, id, 0, 1, 0));
        break;
      case NO_SER_READ:
        bh.consume(db.mBase.readInodeBytes(db.mFileCount - 1, id, 0, 1, 0));
        break;
      case SER_NO_ALLOC_READ:
        bh.consume(db.mBase.readInode(db.mFileCount - 1, id, 0, 1, 0, ts.mInodeRead));
        break;
      case NO_SER_NO_ALLOC_READ:
        bh.consume(db.mBase.readInodeBytes(db.mFileCount - 1, id, 0, 1, 0,
            ts.mInodeRead));
        break;
      default:
        throw new RuntimeException("Unknown mReadType");
    }
  }

  public static void main(String []args) throws RunnerException {
    Options opt = new OptionsBuilder().include(RocksBenchRead.class.getSimpleName())
        .forks(1).addProfiler(StackProfiler.class).build();
    new Runner(opt).run();
  }
}
