Package com.facebook.LinkBench

Source Code of com.facebook.LinkBench.LinkBenchRequest$HistoryKey

/*
* Copyright 2012, Facebook, 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.facebook.LinkBench;

import java.io.PrintStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Properties;
import java.util.Random;
import java.util.concurrent.atomic.AtomicLong;

import org.apache.log4j.Level;
import org.apache.log4j.Logger;

import com.facebook.LinkBench.RealDistribution.DistributionType;
import com.facebook.LinkBench.distributions.AccessDistributions;
import com.facebook.LinkBench.distributions.AccessDistributions.AccessDistribution;
import com.facebook.LinkBench.distributions.ID2Chooser;
import com.facebook.LinkBench.distributions.LogNormalDistribution;
import com.facebook.LinkBench.distributions.ProbabilityDistribution;
import com.facebook.LinkBench.generators.DataGenerator;
import com.facebook.LinkBench.stats.LatencyStats;
import com.facebook.LinkBench.stats.SampledStats;
import com.facebook.LinkBench.util.ClassLoadUtil;


public class LinkBenchRequest implements Runnable {
  private final Logger logger = Logger.getLogger(ConfigUtil.LINKBENCH_LOGGER);
  Properties props;
  LinkStore linkStore;
  NodeStore nodeStore;

  RequestProgress progressTracker;

  long numRequests;
  /** Requests per second: <= 0 for unlimited rate */
  private long requestrate;

  /** Maximum number of failed requests: < 0 for unlimited */
  private long maxFailedRequests;

  /**
   * Time to run benchmark for before collecting stats. Allows
   * caches, etc to warm up.
   */
  private long warmupTime;

  /** Maximum time to run benchmark for, not including warmup time */
  long maxTime;
  int nrequesters;
  int requesterID;
  long maxid1;
  long startid1;
  Level debuglevel;
  long displayFreq_ms;
  long progressFreq_ms;
  String dbid;
  boolean singleAssoc = false;

  // Control data generation settings
  private LogNormalDistribution linkDataSize;
  private DataGenerator linkAddDataGen;
  private DataGenerator linkUpDataGen;
  private LogNormalDistribution nodeDataSize;
  private DataGenerator nodeAddDataGen;
  private DataGenerator nodeUpDataGen;

  // cummulative percentages
  double pc_addlink;
  double pc_deletelink;
  double pc_updatelink;
  double pc_countlink;
  double pc_getlink;
  double pc_getlinklist;
  double pc_addnode;
  double pc_deletenode;
  double pc_updatenode;
  double pc_getnode;

  // Chance of doing historical range query
  double p_historical_getlinklist;

  private static class HistoryKey {
    public final long id1;
    public final long link_type;
    public HistoryKey(long id1, long link_type) {
      super();
      this.id1 = id1;
      this.link_type = link_type;
    }

    public HistoryKey(Link l) {
      this(l.id1, l.link_type);
    }

    @Override
    public int hashCode() {
      final int prime = 31;
      int result = 1;
      result = prime * result + (int) (id1 ^ (id1 >>> 32));
      result = prime * result + (int) (link_type ^ (link_type >>> 32));
      return result;
    }

    @Override
    public boolean equals(Object obj) {
      if (!(obj instanceof HistoryKey))
        return false;
      HistoryKey other = (HistoryKey) obj;
      return id1 == other.id1 && link_type == other.link_type;
    }

  }

  // Cache of last link in lists where full list wasn't retrieved
  ArrayList<Link> listTailHistory;

  // Index of history to avoid duplicates
  HashMap<HistoryKey, Integer> listTailHistoryIndex;

  // Limit of cache size
  private int listTailHistoryLimit;

  // Probability distribution for ids in multiget
  ProbabilityDistribution multigetDist;

  // Statistics
  SampledStats stats;
  LatencyStats latencyStats;

  // Other informational counters
  long numfound = 0;
  long numnotfound = 0;
  long numHistoryQueries = 0;

  /**
   * Random number generator use for generating workload.  If
   * initialized with same seed, should generate same sequence of requests
   * so that tests and benchmarks are repeatable.
   */
  Random rng;

  // Last node id accessed
  long lastNodeId;

  long requestsDone = 0;
  long errors = 0;
  boolean aborted;

  // Access distributions
  private AccessDistribution writeDist; // link writes
  private AccessDistribution writeDistUncorr; // to blend with link writes
  private double writeDistUncorrBlend; // Percentage to used writeDist2 for
  private AccessDistribution readDist; // link reads
  private AccessDistribution readDistUncorr; // to blend with link reads
  private double readDistUncorrBlend; // Percentage to used readDist2 for
  private AccessDistribution nodeReadDist; // node reads
  private AccessDistribution nodeUpdateDist; // node writes
  private AccessDistribution nodeDeleteDist; // node deletes

  private ID2Chooser id2chooser;
  public LinkBenchRequest(LinkStore linkStore,
                          NodeStore nodeStore,
                          Properties props,
                          LatencyStats latencyStats,
                          PrintStream csvStreamOut,
                          RequestProgress progressTracker,
                          Random rng,
                          int requesterID,
                          int nrequesters) {
    assert(linkStore != null);
    if (requesterID < 0 ||  requesterID >= nrequesters) {
      throw new IllegalArgumentException("Bad requester id "
          + requesterID + "/" + nrequesters);
    }

    this.linkStore = linkStore;
    this.nodeStore = nodeStore;
    this.props = props;
    this.latencyStats = latencyStats;
    this.progressTracker = progressTracker;
    this.rng = rng;
    this.nrequesters = nrequesters;
    this.requesterID = requesterID;

    debuglevel = ConfigUtil.getDebugLevel(props);
    dbid = ConfigUtil.getPropertyRequired(props, Config.DBID);
    numRequests = ConfigUtil.getLong(props, Config.NUM_REQUESTS);
    requestrate = ConfigUtil.getLong(props, Config.REQUEST_RATE, 0L);
    maxFailedRequests = ConfigUtil.getLong(props,  Config.MAX_FAILED_REQUESTS, 0L);
    warmupTime = Math.max(0, ConfigUtil.getLong(props, Config.WARMUP_TIME, 0L));
    maxTime = ConfigUtil.getLong(props, Config.MAX_TIME);
    maxid1 = ConfigUtil.getLong(props, Config.MAX_ID);
    startid1 = ConfigUtil.getLong(props, Config.MIN_ID);

    // math functions may cause problems for id1 < 1
    if (startid1 <= 0) {
      throw new LinkBenchConfigError("startid1 must be >= 1");
    }
    if (maxid1 <= startid1) {
      throw new LinkBenchConfigError("maxid1 must be > startid1");
    }

    // is this a single assoc test?
    if (startid1 + 1 == maxid1) {
      singleAssoc = true;
      logger.info("Testing single row assoc read.");
    }

    initRequestProbabilities(props);
    initLinkDataGeneration(props);
    initLinkRequestDistributions(props, requesterID, nrequesters);
    if (pc_getnode > pc_getlinklist) {
      // Load stuff for node workload if needed
      if (nodeStore == null) {
        throw new IllegalArgumentException("nodeStore not provided but non-zero " +
                                         "probability of node operation");
      }
      initNodeDataGeneration(props);
      initNodeRequestDistributions(props);
    }

    displayFreq_ms = ConfigUtil.getLong(props, Config.DISPLAY_FREQ, 60L) * 1000;
    progressFreq_ms = ConfigUtil.getLong(props, Config.PROGRESS_FREQ, 6L) * 1000;
    int maxsamples = ConfigUtil.getInt(props, Config.MAX_STAT_SAMPLES);
    stats = new SampledStats(requesterID, maxsamples, csvStreamOut);

    listTailHistoryLimit = 2048; // Hardcoded limit for now
    listTailHistory = new ArrayList<Link>(listTailHistoryLimit);
    listTailHistoryIndex = new HashMap<HistoryKey, Integer>();
    p_historical_getlinklist = ConfigUtil.getDouble(props,
                        Config.PR_GETLINKLIST_HISTORY, 0.0) / 100;

    lastNodeId = startid1;
  }

  private void initRequestProbabilities(Properties props) {
    pc_addlink = ConfigUtil.getDouble(props, Config.PR_ADD_LINK);
    pc_deletelink = pc_addlink + ConfigUtil.getDouble(props, Config.PR_DELETE_LINK);
    pc_updatelink = pc_deletelink + ConfigUtil.getDouble(props, Config.PR_UPDATE_LINK);
    pc_countlink = pc_updatelink + ConfigUtil.getDouble(props, Config.PR_COUNT_LINKS);
    pc_getlink = pc_countlink + ConfigUtil.getDouble(props, Config.PR_GET_LINK);
    pc_getlinklist = pc_getlink + ConfigUtil.getDouble(props, Config.PR_GET_LINK_LIST);

    pc_addnode = pc_getlinklist + ConfigUtil.getDouble(props, Config.PR_ADD_NODE, 0.0);
    pc_updatenode = pc_addnode + ConfigUtil.getDouble(props, Config.PR_UPDATE_NODE, 0.0);
    pc_deletenode = pc_updatenode + ConfigUtil.getDouble(props, Config.PR_DELETE_NODE, 0.0);
    pc_getnode = pc_deletenode + ConfigUtil.getDouble(props, Config.PR_GET_NODE, 0.0);

    if (Math.abs(pc_getnode - 100.0) > 1e-5) {//compare real numbers
      throw new LinkBenchConfigError("Percentages of request types do not " +
                  "add to 100, only " + pc_getnode + "!");
    }
  }

  private void initLinkRequestDistributions(Properties props, int requesterID,
      int nrequesters) {
    writeDist = AccessDistributions.loadAccessDistribution(props,
            startid1, maxid1, DistributionType.LINK_WRITES);
    readDist = AccessDistributions.loadAccessDistribution(props,
        startid1, maxid1, DistributionType.LINK_READS);

    // Load uncorrelated distributions for blending if needed
    writeDistUncorr = null;
    if (props.containsKey(Config.WRITE_UNCORR_BLEND)) {
      // Ratio of queries to use uncorrelated.  Convert from percentage
      writeDistUncorrBlend = ConfigUtil.getDouble(props,
                Config.WRITE_UNCORR_BLEND) / 100.0;
      if (writeDistUncorrBlend > 0.0) {
        writeDistUncorr = AccessDistributions.loadAccessDistribution(props,
            startid1, maxid1, DistributionType.LINK_WRITES_UNCORR);
      }
    }

    readDistUncorr = null;
    if (props.containsKey(Config.READ_UNCORR_BLEND)) {
      // Ratio of queries to use uncorrelated.  Convert from percentage
      readDistUncorrBlend = ConfigUtil.getDouble(props,
                Config.READ_UNCORR_BLEND) / 100.0;
      if (readDistUncorrBlend > 0.0) {
        readDistUncorr = AccessDistributions.loadAccessDistribution(props,
            startid1, maxid1, DistributionType.LINK_READS_UNCORR);
      }
    }

    id2chooser = new ID2Chooser(props, startid1, maxid1,
                                nrequesters, requesterID);

    // Distribution of #id2s per multiget
    String multigetDistClass = props.getProperty(Config.LINK_MULTIGET_DIST);
    if (multigetDistClass != null && multigetDistClass.trim().length() != 0) {
      int multigetMin = ConfigUtil.getInt(props, Config.LINK_MULTIGET_DIST_MIN);
      int multigetMax = ConfigUtil.getInt(props, Config.LINK_MULTIGET_DIST_MAX);
      try {
        multigetDist = ClassLoadUtil.newInstance(multigetDistClass,
                                            ProbabilityDistribution.class);
        multigetDist.init(multigetMin, multigetMax, props,
                                             Config.LINK_MULTIGET_DIST_PREFIX);
      } catch (ClassNotFoundException e) {
        logger.error(e);
        throw new LinkBenchConfigError("Class" + multigetDistClass +
            " could not be loaded as ProbabilityDistribution");
      }
    } else {
      multigetDist = null;
    }
  }

  private void initLinkDataGeneration(Properties props) {
    try {
      double medLinkDataSize = ConfigUtil.getDouble(props,
                                            Config.LINK_DATASIZE);
      linkDataSize = new LogNormalDistribution();
      linkDataSize.init(0, LinkStore.MAX_LINK_DATA, medLinkDataSize,
                           Config.LINK_DATASIZE_SIGMA);
      linkAddDataGen = ClassLoadUtil.newInstance(
          ConfigUtil.getPropertyRequired(props, Config.LINK_ADD_DATAGEN),
          DataGenerator.class);
      linkAddDataGen.init(props, Config.LINK_ADD_DATAGEN_PREFIX);

      linkUpDataGen = ClassLoadUtil.newInstance(
          ConfigUtil.getPropertyRequired(props, Config.LINK_UP_DATAGEN),
          DataGenerator.class);
      linkUpDataGen.init(props, Config.LINK_UP_DATAGEN_PREFIX);
    } catch (ClassNotFoundException ex) {
      logger.error(ex);
      throw new LinkBenchConfigError("Error loading data generator class: "
            + ex.getMessage());
    }
  }

  private void initNodeRequestDistributions(Properties props) {
    try {
      nodeReadDist  = AccessDistributions.loadAccessDistribution(props,
        startid1, maxid1, DistributionType.NODE_READS);
    } catch (LinkBenchConfigError e) {
      // Not defined
      logger.info("Node access distribution not configured: " +
          e.getMessage());
      throw new LinkBenchConfigError("Node read distribution not " +
            "configured but node read operations have non-zero probability");
    }

    try {
      nodeUpdateDist  = AccessDistributions.loadAccessDistribution(props,
        startid1, maxid1, DistributionType.NODE_UPDATES);
    } catch (LinkBenchConfigError e) {
      // Not defined
      logger.info("Node access distribution not configured: " +
              e.getMessage());
      throw new LinkBenchConfigError("Node write distribution not " +
            "configured but node write operations have non-zero probability");
    }

    try {
      nodeDeleteDist = AccessDistributions.loadAccessDistribution(props,
        startid1, maxid1, DistributionType.NODE_DELETES);
    } catch (LinkBenchConfigError e) {
      // Not defined
      logger.info("Node delete distribution not configured: " +
              e.getMessage());
      throw new LinkBenchConfigError("Node delete distribution not " +
            "configured but node write operations have non-zero probability");
    }
  }

  private void initNodeDataGeneration(Properties props) {
    try {
      double medNodeDataSize = ConfigUtil.getDouble(props,
                                              Config.NODE_DATASIZE);
      nodeDataSize = new LogNormalDistribution();
      nodeDataSize.init(0, NodeStore.MAX_NODE_DATA, medNodeDataSize,
                        Config.NODE_DATASIZE_SIGMA);

      String dataGenClass = ConfigUtil.getPropertyRequired(props,
                                         Config.NODE_ADD_DATAGEN);
      nodeAddDataGen = ClassLoadUtil.newInstance(dataGenClass,
                                                 DataGenerator.class);
      nodeAddDataGen.init(props, Config.NODE_ADD_DATAGEN_PREFIX);

      dataGenClass = ConfigUtil.getPropertyRequired(props,
                        Config.NODE_UP_DATAGEN);
      nodeUpDataGen = ClassLoadUtil.newInstance(dataGenClass,
                                                 DataGenerator.class);
      nodeUpDataGen.init(props, Config.NODE_UP_DATAGEN_PREFIX);
    } catch (ClassNotFoundException ex) {
      logger.error(ex);
      throw new LinkBenchConfigError("Error loading data generator class: "
            + ex.getMessage());
    }
  }

  public long getRequestsDone() {
    return requestsDone;
  }

  public boolean didAbort() {
    return aborted;
  }

  // gets id1 for the request based on desired distribution
  private long chooseRequestID(DistributionType type, long previousId1) {
    AccessDistribution dist;
    switch (type) {
    case LINK_READS:
      // Blend between distributions if needed
      if (readDistUncorr == null || rng.nextDouble() >= readDistUncorrBlend) {
        dist = readDist;
      } else {
        dist = readDistUncorr;
      }
      break;
    case LINK_WRITES:
      // Blend between distributions if needed
      if (writeDistUncorr == null || rng.nextDouble() >= writeDistUncorrBlend) {
        dist = writeDist;
      } else {
        dist = writeDistUncorr;
      }
      break;
    case LINK_WRITES_UNCORR:
      dist = writeDistUncorr;
      break;
    case NODE_READS:
      dist = nodeReadDist;
      break;
    case NODE_UPDATES:
      dist = nodeUpdateDist;
      break;
    case NODE_DELETES:
      dist = nodeDeleteDist;
      break;
    default:
      throw new RuntimeException("Unknown value for type: " + type);
    }
    long newid1 = dist.nextID(rng, previousId1);
    // Distribution responsible for generating number in range
    assert((newid1 >= startid1) && (newid1 < maxid1));
    if (Level.TRACE.isGreaterOrEqual(debuglevel)) {
      logger.trace("id1 generated = " + newid1 +
         " for access distribution: " + dist.getClass().getName() + ": " +
         dist.toString());
    }

    if (dist.getShuffler() != null) {
      // Shuffle to go from position in space ranked from most to least accessed,
      // to the real id space
      newid1 = startid1 + dist.getShuffler().permute(newid1 - startid1);
    }
    return newid1;
  }

  /**
   * Randomly choose a single request and execute it, updating statistics
   * @param recordStats If true, record latency and other stats.
   * @return true if successful, false on error
   */
  private boolean oneRequest(boolean recordStats) {

    double r = rng.nextDouble() * 100.0;

    long starttime = 0;
    long endtime = 0;

    LinkBenchOp type = LinkBenchOp.UNKNOWN; // initialize to invalid value
    Link link = new Link();
    try {

      if (r <= pc_addlink) {
        // generate add request
        type = LinkBenchOp.ADD_LINK;
        link.id1 = chooseRequestID(DistributionType.LINK_WRITES, link.id1);
        link.link_type = id2chooser.chooseRandomLinkType(rng);
        link.id2 = id2chooser.chooseForOp(rng, link.id1, link.link_type,
                                                ID2Chooser.P_ADD_EXIST);
        link.visibility = LinkStore.VISIBILITY_DEFAULT;
        link.version = 0;
        link.time = System.currentTimeMillis();
        link.data = linkAddDataGen.fill(rng,
                                      new byte[(int)linkDataSize.choose(rng)]);

        starttime = System.nanoTime();
        // no inverses for now
        boolean alreadyExists = linkStore.addLink(dbid, link, true);
        boolean added = !alreadyExists;
        endtime = System.nanoTime();
        if (Level.TRACE.isGreaterOrEqual(debuglevel)) {
          logger.trace("addLink id1=" + link.id1 + " link_type="
                    + link.link_type + " id2=" + link.id2 + " added=" + added);
        }
      } else if (r <= pc_deletelink) {
        type = LinkBenchOp.DELETE_LINK;
        long id1 = chooseRequestID(DistributionType.LINK_WRITES, link.id1);
        long link_type = id2chooser.chooseRandomLinkType(rng);
        long id2 = id2chooser.chooseForOp(rng, id1, link_type,
                                          ID2Chooser.P_DELETE_EXIST);
        starttime = System.nanoTime();
        linkStore.deleteLink(dbid, id1, link_type, id2, true, // no inverse
            false);
        endtime = System.nanoTime();
        if (Level.TRACE.isGreaterOrEqual(debuglevel)) {
          logger.trace("deleteLink id1=" + id1 + " link_type=" + link_type
                     + " id2=" + id2);
        }
      } else if (r <= pc_updatelink) {
        type = LinkBenchOp.UPDATE_LINK;
        link.id1 = chooseRequestID(DistributionType.LINK_WRITES, link.id1);
        link.link_type = id2chooser.chooseRandomLinkType(rng);
        // Update one of the existing links
        link.id2 = id2chooser.chooseForOp(rng, link.id1, link.link_type,
                                              ID2Chooser.P_UPDATE_EXIST);
        link.visibility = LinkStore.VISIBILITY_DEFAULT;
        link.version = 0;
        link.time = System.currentTimeMillis();
        link.data = linkUpDataGen.fill(rng,
                            new byte[(int)linkDataSize.choose(rng)]);

        starttime = System.nanoTime();
        // no inverses for now
        boolean found1 = linkStore.addLink(dbid, link, true);
        boolean found = found1;
        endtime = System.nanoTime();
        if (Level.TRACE.isGreaterOrEqual(debuglevel)) {
          logger.trace("updateLink id1=" + link.id1 + " link_type="
                + link.link_type + " id2=" + link.id2 + " found=" + found);
        }
      } else if (r <= pc_countlink) {

        type = LinkBenchOp.COUNT_LINK;

        long id1 = chooseRequestID(DistributionType.LINK_READS, link.id1);
        long link_type = id2chooser.chooseRandomLinkType(rng);
        starttime = System.nanoTime();
        long count = linkStore.countLinks(dbid, id1, link_type);
        endtime = System.nanoTime();
        if (Level.TRACE.isGreaterOrEqual(debuglevel)) {
          logger.trace("countLink id1=" + id1 + " link_type=" + link_type
                     + " count=" + count);
        }
      } else if (r <= pc_getlink) {

        type = LinkBenchOp.MULTIGET_LINK;

        long id1 = chooseRequestID(DistributionType.LINK_READS, link.id1);
        long link_type = id2chooser.chooseRandomLinkType(rng);
        int nid2s = 1;
        if (multigetDist != null) {
          nid2s = (int)multigetDist.choose(rng);
        }
        long id2s[] = id2chooser.chooseMultipleForOp(rng, id1, link_type, nid2s,
                                                 ID2Chooser.P_GET_EXIST);

        starttime = System.nanoTime();
        int found = getLink(id1, link_type, id2s);
        assert(found >= 0 && found <= nid2s);
        endtime = System.nanoTime();

        if (found > 0) {
          numfound += found;
        } else {
          numnotfound += nid2s - found;
        }

      } else if (r <= pc_getlinklist) {

        type = LinkBenchOp.GET_LINKS_LIST;
        Link links[];

        if (rng.nextDouble() < p_historical_getlinklist &&
                    !this.listTailHistory.isEmpty()) {
          links = getLinkListTail();
        } else {
          long id1 = chooseRequestID(DistributionType.LINK_READS, link.id1);
          long link_type = id2chooser.chooseRandomLinkType(rng);
          starttime = System.nanoTime();
          links = getLinkList(id1, link_type);
          endtime = System.nanoTime();
        }

        int count = ((links == null) ? 0 : links.length);
        if (recordStats) {
          stats.addStats(LinkBenchOp.RANGE_SIZE, count, false);
        }
      } else if (r <= pc_addnode) {
        type = LinkBenchOp.ADD_NODE;
        Node newNode = createAddNode();
        starttime = System.nanoTime();
        lastNodeId = nodeStore.addNode(dbid, newNode);
        endtime = System.nanoTime();
        if (Level.TRACE.isGreaterOrEqual(debuglevel)) {
          logger.trace("addNode " + newNode);
        }
      } else if (r <= pc_updatenode) {
        type = LinkBenchOp.UPDATE_NODE;
        // Choose an id that has previously been created (but might have
        // been since deleted
        long upId = chooseRequestID(DistributionType.NODE_UPDATES,
                                     lastNodeId);
        // Generate new data randomly
        Node newNode = createUpdateNode(upId);

        starttime = System.nanoTime();
        boolean changed = nodeStore.updateNode(dbid, newNode);
        endtime = System.nanoTime();
        lastNodeId = upId;
        if (Level.TRACE.isGreaterOrEqual(debuglevel)) {
          logger.trace("updateNode " + newNode + " changed=" + changed);
        }
      } else if (r <= pc_deletenode) {
        type = LinkBenchOp.DELETE_NODE;
        long idToDelete = chooseRequestID(DistributionType.NODE_DELETES,
                                          lastNodeId);
        starttime = System.nanoTime();
        boolean deleted = nodeStore.deleteNode(dbid, LinkStore.DEFAULT_NODE_TYPE,
                                                     idToDelete);
        endtime = System.nanoTime();
        lastNodeId = idToDelete;
        if (Level.TRACE.isGreaterOrEqual(debuglevel)) {
          logger.trace("deleteNode " + idToDelete + " deleted=" + deleted);
        }
      } else if (r <= pc_getnode) {
        type = LinkBenchOp.GET_NODE;
        starttime = System.nanoTime();
        long idToFetch = chooseRequestID(DistributionType.NODE_READS,
                                         lastNodeId);
        Node fetched = nodeStore.getNode(dbid, LinkStore.DEFAULT_NODE_TYPE, idToFetch);
        endtime = System.nanoTime();
        lastNodeId = idToFetch;
        if (Level.TRACE.isGreaterOrEqual(debuglevel)) {
          if (fetched == null) {
            logger.trace("getNode " + idToFetch + " not found");
          } else {
            logger.trace("getNode " + fetched);
          }
        }
      } else {
        logger.error("No-op in requester: last probability < 1.0");
        return false;
      }


      // convert to microseconds
      long timetaken = (endtime - starttime)/1000;

      if (recordStats) {
        // record statistics
        stats.addStats(type, timetaken, false);
        latencyStats.recordLatency(requesterID, type, timetaken);
      }

      return true;
    } catch (Throwable e){//Catch exception if any

      long endtime2 = System.nanoTime();

      long timetaken2 = (endtime2 - starttime)/1000;

      logger.error(type.displayName() + " error " +
                         e.getMessage(), e);
      if (recordStats) {
        stats.addStats(type, timetaken2, true);
      }
      linkStore.clearErrors(requesterID);
      return false;
    }
  }

  /**
   * Create a new node for adding to database
   * @return
   */
  private Node createAddNode() {
    byte data[] = nodeAddDataGen.fill(rng, new byte[(int)nodeDataSize.choose(rng)]);
    return new Node(-1, LinkStore.DEFAULT_NODE_TYPE, 1,
                    (int)(System.currentTimeMillis()/1000), data);
  }

  /**
   * Create new node for updating in database
   */
  private Node createUpdateNode(long id) {
    byte data[] = nodeUpDataGen.fill(rng, new byte[(int)nodeDataSize.choose(rng)]);
    return new Node(id, LinkStore.DEFAULT_NODE_TYPE, 2,
                    (int)(System.currentTimeMillis()/1000), data);
  }

  @Override
  public void run() {
    logger.info("Requester thread #" + requesterID + " started: will do "
        + numRequests + " ops after " + warmupTime + " second warmup");
    logger.debug("Requester thread #" + requesterID + " first random number "
                  + rng.nextLong());

    try {
      this.linkStore.initialize(props, Phase.REQUEST, requesterID);
      if (this.nodeStore != null && this.nodeStore != this.linkStore) {
        this.nodeStore.initialize(props, Phase.REQUEST, requesterID);
      }
    } catch (Exception e) {
      logger.error("Error while initializing store", e);
      throw new RuntimeException(e);
    }

    long warmupStartTime = System.currentTimeMillis();
    boolean warmupDone = warmupTime <= 0;
    long benchmarkStartTime;
    if (!warmupDone) {
      benchmarkStartTime = warmupStartTime + warmupTime * 1000;
    } else {
      benchmarkStartTime = warmupStartTime;
    }
    long endTime = benchmarkStartTime + maxTime * 1000;
    long lastUpdate = warmupStartTime;
    long curTime = warmupStartTime;

    long i;

    if (singleAssoc) {
      LinkBenchOp type = LinkBenchOp.UNKNOWN;
      try {
        Link link = new Link();
        // add a single assoc to the database
        link.id1 = 45;
        link.id1 = 46;
        type = LinkBenchOp.ADD_LINK;
        // no inverses for now
        linkStore.addLink(dbid, link, true);

        // read this assoc from the database over and over again
        type = LinkBenchOp.MULTIGET_LINK;
        for (i = 0; i < numRequests; i++) {
          int found = getLink(link.id1, link.link_type,
                                  new long[]{link.id2});
          if (found == 1) {
            requestsDone++;
          } else {
            logger.warn("ThreadID = " + requesterID +
                               " not found link for id1=45");
          }
        }
      } catch (Throwable e) {
        logger.error(type.displayName() + "error " +
                         e.getMessage(), e);
        aborted = true;
      }
      closeStores();
      return;
    }

    long warmupRequests = 0;
    long requestsSinceLastUpdate = 0;
    long lastStatDisplay_ms = curTime;
    long reqTime_ns = System.nanoTime();
    double requestrate_ns = ((double)requestrate)/1e9;
    while (requestsDone < numRequests) {
      if (requestrate > 0) {
        reqTime_ns = Timer.waitExpInterval(rng, reqTime_ns, requestrate_ns);
      }
      boolean success = oneRequest(warmupDone);
      if (!success) {
        errors++;
        if (maxFailedRequests >= 0 && errors > maxFailedRequests) {
          logger.error(String.format("Requester #%d aborting: %d failed requests" +
              " (out of %d total) ", requesterID, errors, requestsDone));
          aborted = true;
          break;
        }
      }

      curTime = System.currentTimeMillis();

      // Track requests done
      if (warmupDone) {
        requestsDone++;
        requestsSinceLastUpdate++;
        if (requestsSinceLastUpdate >= RequestProgress.THREAD_REPORT_INTERVAL) {
          progressTracker.update(requestsSinceLastUpdate);
          requestsSinceLastUpdate = 0;
        }
      } else {
        warmupRequests++;
      }

      // Per-thread periodic progress updates
      if (curTime > lastUpdate + progressFreq_ms) {
        if (warmupDone) {
          logger.info(String.format("Requester #%d %d/%d requests done",
              requesterID, requestsDone, numRequests));
          lastUpdate = curTime;
        } else {
          logger.info(String.format("Requester #%d warming up.  " +
              " %d warmup requests done. %d/%d seconds of warmup done",
              requesterID, warmupRequests, (curTime - warmupStartTime) / 1000,
              warmupTime));
          lastUpdate = curTime;
        }
      }

      // Per-thread periodic stat dumps after warmup done
      if (warmupDone && (lastStatDisplay_ms + displayFreq_ms) <= curTime) {
        displayStats(lastStatDisplay_ms, curTime);
        stats.resetSamples();
        lastStatDisplay_ms = curTime;
      }

      // Check if warmup completed
      if (!warmupDone && curTime >= benchmarkStartTime) {
        warmupDone = true;
        lastUpdate = curTime;
        lastStatDisplay_ms = curTime;
        requestsSinceLastUpdate = 0;
        logger.info(String.format("Requester #%d warmup finished " +
            " after %d warmup requests.  0/%d requests done",
            requesterID, warmupRequests, numRequests));
        lastUpdate = curTime;
      }

      // Enforce time limit
      if (curTime > endTime) {
        logger.info(String.format("Requester #%d: time limit of %ds elapsed" +
              ", shutting down.", requesterID, maxTime));
        break;
      }
    }

    // Do final update of statistics
    progressTracker.update(requestsSinceLastUpdate);
    displayStats(lastStatDisplay_ms, System.currentTimeMillis());

    // Report final stats
    logger.info("ThreadID = " + requesterID +
                       " total requests = " + requestsDone +
                       " requests/second = " + ((1000 * requestsDone)/
                                                Math.max(1, (curTime - benchmarkStartTime))) +
                       " found = " + numfound +
                       " not found = " + numnotfound +
                       " history queries = " + numHistoryQueries + "/" +
                                   stats.getCount(LinkBenchOp.GET_LINKS_LIST));
    closeStores();
  }

  /**
   * Close datastores before finishing
   */
  private void closeStores() {
    linkStore.close();
    if (nodeStore != null && nodeStore != linkStore) {
      nodeStore.close();
    }
  }

  private void displayStats(long lastStatDisplay_ms, long now_ms) {
    stats.displayStats(lastStatDisplay_ms, now_ms,
        Arrays.asList(
        LinkBenchOp.MULTIGET_LINK, LinkBenchOp.GET_LINKS_LIST,
        LinkBenchOp.COUNT_LINK,
        LinkBenchOp.UPDATE_LINK, LinkBenchOp.ADD_LINK,
        LinkBenchOp.RANGE_SIZE, LinkBenchOp.ADD_NODE,
        LinkBenchOp.UPDATE_NODE, LinkBenchOp.DELETE_NODE,
        LinkBenchOp.GET_NODE));
  }

  int getLink(long id1, long link_type, long id2s[]) throws Exception {
    Link links[] = linkStore.multigetLinks(dbid, id1, link_type, id2s);
    return links == null ? 0 : links.length;
  }

  Link[] getLinkList(long id1, long link_type) throws Exception {
    Link links[] = linkStore.getLinkList(dbid, id1, link_type);
    if (Level.TRACE.isGreaterOrEqual(debuglevel)) {
       logger.trace("getLinkList(id1=" + id1 + ", link_type="  + link_type
                     + ") => count=" + (links == null ? 0 : links.length));
    }
    // If there were more links than limit, record
    if (links != null && links.length >= linkStore.getRangeLimit()) {
      Link lastLink = links[links.length-1];
      if (Level.TRACE.isGreaterOrEqual(debuglevel)) {
        logger.trace("Maybe more history for (" + id1 +"," +
                      link_type + " older than " + lastLink.time);
      }

      addTailCacheEntry(lastLink);
    }
    return links;
  }

  Link[] getLinkListTail() throws Exception {
    assert(!listTailHistoryIndex.isEmpty());
    assert(!listTailHistory.isEmpty());
    int choice = rng.nextInt(listTailHistory.size());
    Link prevLast = listTailHistory.get(choice);

    // Get links past the oldest last retrieved
    Link links[] = linkStore.getLinkList(dbid, prevLast.id1,
        prevLast.link_type, 0, prevLast.time, 1, linkStore.getRangeLimit());

    if (Level.TRACE.isGreaterOrEqual(debuglevel)) {
      logger.trace("getLinkListTail(id1=" + prevLast.id1 + ", link_type="
                + prevLast.link_type + ", max_time=" + prevLast.time
                + " => count=" + (links == null ? 0 : links.length));
   }
    if (Level.TRACE.isGreaterOrEqual(debuglevel)) {
      logger.trace("Historical range query for (" + prevLast.id1 +"," +
                    prevLast.link_type + " older than " + prevLast.time +
                    ": " + (links == null ? 0 : links.length) + " results");
    }

    if (links != null && links.length == linkStore.getRangeLimit()) {
      // There might be yet more history
      Link last = links[links.length-1];
      if (Level.TRACE.isGreaterOrEqual(debuglevel)) {
        logger.trace("might be yet more history for (" + last.id1 +"," +
                      last.link_type + " older than " + last.time);
      }
      // Update in place
      listTailHistory.set(choice, last.clone());
    } else {
      // No more history after this, remove from cache
      removeTailCacheEntry(choice, null);
    }
    numHistoryQueries++;
    return links;
  }

  /**
   * Add a new link to the history cache, unless already present
   * @param lastLink the last (i.e. lowest timestamp) link retrieved
   */
  private void addTailCacheEntry(Link lastLink) {
    HistoryKey key = new HistoryKey(lastLink);
    if (listTailHistoryIndex.containsKey(key)) {
      // Already present
      return;
    }

    if (listTailHistory.size() < listTailHistoryLimit) {
      listTailHistory.add(lastLink.clone());
      listTailHistoryIndex.put(key, listTailHistory.size() - 1);
    } else {
      // Need to evict entry
      int choice = rng.nextInt(listTailHistory.size());
      removeTailCacheEntry(choice, lastLink.clone());
    }
  }

  /**
   * Remove or replace entry in listTailHistory and update index
   * @param pos index of entry in listTailHistory
   * @param repl replace with this if not null
   */
  private void removeTailCacheEntry(int pos, Link repl) {
    Link entry = listTailHistory.get(pos);
    if (pos == listTailHistory.size() - 1) {
      // removing from last position, don't need to fill gap
      listTailHistoryIndex.remove(new HistoryKey(entry));
      int lastIx = listTailHistory.size() - 1;
      if (repl == null) {
        listTailHistory.remove(lastIx);
      } else {
        listTailHistory.set(lastIx, repl);
        listTailHistoryIndex.put(new HistoryKey(repl), lastIx);
      }
    } else {
      if (repl == null) {
        // Replace with last entry in cache to fill gap
        repl = listTailHistory.get(listTailHistory.size() - 1);
        listTailHistory.remove(listTailHistory.size() - 1);
      }
      listTailHistory.set(pos, repl);
      listTailHistoryIndex.put(new HistoryKey(repl), pos);
    }
  }

  public static class RequestProgress {
    // How many ops before a thread should register its progress
    static final int THREAD_REPORT_INTERVAL = 250;
    /** How many ops before a progress update should be printed to console */
    private final long interval;

    private final Logger progressLogger;

    private long totalRequests;
    private final AtomicLong requestsDone;

    private long benchmarkStartTime;
    private long warmupTime_s;
    private long timeLimit_s;

    public RequestProgress(Logger progressLogger, long totalRequests,
                            long timeLimit_s, long warmupTime_s, long interval) {
      this.interval = interval;
      this.progressLogger = progressLogger;
      this.totalRequests = totalRequests;
      this.requestsDone = new AtomicLong();
      this.timeLimit_s = timeLimit_s;
      this.warmupTime_s = warmupTime_s;
    }

    public void startTimer() {
      benchmarkStartTime = System.currentTimeMillis() + warmupTime_s * 1000;
    }

    public long getBenchmarkStartTime() {
      return benchmarkStartTime;
    }

    public void update(long requestIncr) {
      long curr = requestsDone.addAndGet(requestIncr);
      long prev = curr - requestIncr;

      if ((curr / interval) > (prev / interval) || curr == totalRequests) {
        float progressPercent = ((float) curr) / totalRequests * 100;
        long now = System.currentTimeMillis();
        long elapsed = now - benchmarkStartTime;
        float elapsed_s = ((float) elapsed) / 1000;
        float limitPercent = (elapsed_s / ((float) timeLimit_s)) * 100;
        float rate = curr / ((float)elapsed_s);
        progressLogger.info(String.format(
            "%d/%d requests finished: %.1f%% complete at %.1f ops/sec" +
            " %.1f/%d secs elapsed: %.1f%% of time limit used",
            curr, totalRequests, progressPercent, rate,
            elapsed_s, timeLimit_s, limitPercent));

      }
    }
  }

  public static RequestProgress createProgress(Logger logger,
       Properties props) {
    long total_requests = ConfigUtil.getLong(props, Config.NUM_REQUESTS)
                      * ConfigUtil.getLong(props, Config.NUM_REQUESTERS);
    long progressInterval = ConfigUtil.getLong(props, Config.REQ_PROG_INTERVAL,
                                               10000L);
    long warmupTime = ConfigUtil.getLong(props, Config.WARMUP_TIME, 0L);
    long maxTime = ConfigUtil.getLong(props, Config.MAX_TIME);
    return new RequestProgress(logger, total_requests,
              maxTime, warmupTime, progressInterval);
  }
}
TOP

Related Classes of com.facebook.LinkBench.LinkBenchRequest$HistoryKey

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.
d', 'pageview');