Package edu.berkeley.sparrow.examples

Source Code of edu.berkeley.sparrow.examples.ProtoBackend

/*
* Copyright 2013 The Regents of The University California
*
* 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 edu.berkeley.sparrow.examples;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Random;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;

import joptsimple.OptionParser;
import joptsimple.OptionSet;

import org.apache.commons.configuration.Configuration;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.apache.thrift.TException;

import com.google.common.collect.Lists;

import edu.berkeley.sparrow.daemon.nodemonitor.NodeMonitorThrift;
import edu.berkeley.sparrow.daemon.util.TClients;
import edu.berkeley.sparrow.daemon.util.TServers;
import edu.berkeley.sparrow.thrift.BackendService;
import edu.berkeley.sparrow.thrift.NodeMonitorService.Client;
import edu.berkeley.sparrow.thrift.TFullTaskId;
import edu.berkeley.sparrow.thrift.TUserGroupInfo;

/**
* A prototype Sparrow backend.
*
* This backend is capable of performing a number of benchmark tasks, each representing
* distinct resource consumption profiles. It initiates a thrift server with a bounded
* size thread pool (of at most {@code WORKER_THREADS} threads). To makes sure that
* we never queue tasks, we additionally spawn a new thread each time a task is launched.
* In the future, we will have launchTask() directly execute the task and rely on queuing
* in the underlying thread pool to queue if task launches exceed capacity.
*/
public class ProtoBackend implements BackendService.Iface {
  /** Benchmark which, on each iteration, runs 1 million random floating point
   *  multiplications.*/
  public static int BENCHMARK_TYPE_FP_CPU = 1;
  /** Benchmark which allocates a heap buffer of 200 million bytes, then on each iteration
   *  accesses 1 million contiguous bytes of the buffer, starting at a random offset.*/
  public static int BENCHMARK_TYPE_RANDOM_MEMACCESS = 2;
  // NOTE: we do not use an enum for the above because it is not possible to serialize
  // an enum with our current simple serialization technique.

  /** Tracks the total number of tasks launched since execution began. Updated on
   * each task launch. This is helpful for diagnosing unwanted queuing in various parts
   * of the system (i.e. if we notice the backend is launching fewer tasks than we expect
   * based on the frontend task launch rate). */
  public static AtomicInteger numTasks = new AtomicInteger(0);
  public static long startTime = -1;

  private static final int DEFAULT_LISTEN_PORT = 20101;

  /**
   * This indicates how many threads can concurrently be answering function calls
   * from the NM. Each task is launched in a new thread from one of these threads. If tasks
   * launches arrive fast enough that all worker threads are concurrently executing
   * a task, this will queue. We currently launch new threads for each task to prevent
   * this from happening.
   */
  private static final int THRIFT_WORKER_THREADS = 16;
  private static final int TASK_WORKER_THREADS = 16;
  private static final String APP_ID = "testApp";

  /** We assume we are speaking to local Node Manager. */
  private static final String NM_HOST = "localhost";
  private static int NM_PORT;

  private static Client client;

  private static final Logger LOG = Logger.getLogger(ProtoBackend.class);
  private static final ExecutorService executor =
      Executors.newFixedThreadPool(TASK_WORKER_THREADS);

  /**
   * Keeps track of finished tasks.
   *
   *  A single thread pulls items off of this queue and uses
   * the client to notify the node monitor that tasks have finished.
   */
  private final BlockingQueue<TFullTaskId> finishedTasks = new LinkedBlockingQueue<TFullTaskId>();

  /**
   * Thread that sends taskFinished() RPCs to the node monitor.
   *
   * We do this in a single thread so that we just need a single client to the node monitor
   * and don't need to create a new client for each task.
   */
  private class TasksFinishedRpcRunnable implements Runnable {
    @Override
    public void run() {
      while (true) {
        try {
          TFullTaskId task = finishedTasks.take();
          client.tasksFinished(Lists.newArrayList(task));
        } catch (InterruptedException e) {
          LOG.error("Error taking a task from the queue: " + e.getMessage());
        } catch (TException e) {
          LOG.error("Error with tasksFinished() RPC:" + e.getMessage());
        }
      }
    }
  }

  /**
   * Thread spawned for each task. It runs for a given amount of time (and adds
   * its resources to the total resources for that time) then stops. It updates
   * the NodeMonitor when it launches and again when it finishes.
   */
  private class TaskRunnable implements Runnable {
    private int benchmarkId;
    private int benchmarkIterations;
    private TFullTaskId taskId;

    public TaskRunnable(String requestId, TFullTaskId taskId, ByteBuffer message) {
      this.benchmarkId = message.getInt();
      this.benchmarkIterations = message.getInt();
      this.taskId = taskId;
    }

    @Override
    public void run() {
      if (startTime == -1) {
        startTime = System.currentTimeMillis();
      }

      long taskStart = System.currentTimeMillis();

      int tasks = numTasks.addAndGet(1);
      double taskRate = ((double) tasks) * 1000 /
          (System.currentTimeMillis() - startTime);
      LOG.debug("Aggregate task rate: " + taskRate + " (" + tasks + " launched)");

      Random r = new Random();

      long benchmarkStart = System.currentTimeMillis();
      runBenchmark(benchmarkId, benchmarkIterations, r);
      LOG.debug("Benchmark runtime: " + (System.currentTimeMillis() - benchmarkStart));

      finishedTasks.add(taskId);
      LOG.debug("Task running for " + (System.currentTimeMillis() - taskStart) + " ms");
    }
  }

  /**
   * Run the benchmark identified by {@code benchmarkId} for {@code iterations}
   * iterations using random generator {@code r}. Return true if benchmark is recognized
   * and false otherwise.
   */
  public static boolean runBenchmark(int benchmarkId, int iterations, Random r) {
    if (benchmarkId == BENCHMARK_TYPE_RANDOM_MEMACCESS) {
      LOG.debug("Running random access benchmark for " + iterations + " iterations.");
      runRandomMemAcessBenchmark(iterations, r);
    } else if (benchmarkId == BENCHMARK_TYPE_FP_CPU) {
      LOG.debug("Running CPU benchmark for " + iterations + " iterations.");
     runFloatingPointBenchmark(iterations, r);
    } else {
      LOG.error("Received unrecognized benchmark type");
      return false;
    }
    return true;
  }

  /**
   * Benchmark that runs random floating point multiplications for the specified amount of
   * "iterations", where each iteration is one millisecond.
   */
  public static void runFloatingPointBenchmark(int iterations, Random r) {
    int runtimeMillis = iterations;
    long startTime = System.nanoTime();
    int opsPerIteration = 1000;
    /* We keep a running result here and print it out so that the JVM doesn't
     * optimize all this computation away. */
    float result = r.nextFloat();
    while ((System.nanoTime() - startTime) / (1000.0 * 1000.0) < runtimeMillis) {
      for (int j = 0; j < opsPerIteration; j++) {
        // On each iteration, perform a floating point multiplication
        float x = r.nextFloat();
        float y = r.nextFloat();
        result += (x * y);
      }
    }
    LOG.debug("Benchmark result " + result);
  }

  /** Benchmark which allocates a heap buffer of 200 million bytes, then on each iteration
   *  accesses 1 million contiguous bytes of the buffer, starting at a random offset.*/
  public static void runRandomMemAcessBenchmark(int iterations, Random r) {
    // 2 hundred million byte buffer
    int buffSize = 1000 * 1000 * 200;
    byte[] buff = new byte[buffSize];
    // scan 1 million bytes at a time
    int runLength = 1000 * 1000;
    // We keep a running result here and print it out so that the JVM doesn't
    // optimize all this computation away.
    byte result = 1;
    for (int i = 0; i < iterations; i++) {
      // On each iteration, start at a random index, and scan runLength contiguous
      // bytes, potentially wrapping if we hit the end of the buffer.
      int start = r.nextInt(buff.length);
      for (int j = 0; j < runLength; j++) {
        result = (byte) (result ^ buff[(start + j) % (buff.length - 1)]);
      }
    }
    LOG.debug("Benchmark result " + result);
  }

  /**
   * Initializes the backend by registering with the node monitor.
   *
   * Also starts a thread that handles finished tasks (by sending an RPC to the node monitor).
   */
  public void initialize(int listenPort) {
    // Register server.
    try {
      client = TClients.createBlockingNmClient(NM_HOST, NM_PORT);
    } catch (IOException e) {
      LOG.debug("Error creating Thrift client: " + e.getMessage());
    }

    try {
      client.registerBackend(APP_ID, "localhost:" + listenPort);
      LOG.debug("Client successfully registered");
    } catch (TException e) {
      LOG.debug("Error while registering backend: " + e.getMessage());
    }

    new Thread(new TasksFinishedRpcRunnable()).start();
  }

  @Override
  public void launchTask(ByteBuffer message, TFullTaskId taskId,
      TUserGroupInfo user) throws TException {
    LOG.info("Submitting task " + taskId.getTaskId() + " at " + System.currentTimeMillis());

    // Note we ignore user here
    executor.submit(new TaskRunnable(
        taskId.requestId, taskId, message));
  }

  public static void main(String[] args) throws IOException, TException {
    OptionParser parser = new OptionParser();
    parser.accepts("c", "configuration file").
      withRequiredArg().ofType(String.class);
    parser.accepts("help", "print help statement");
    OptionSet options = parser.parse(args);

    if (options.has("help")) {
      parser.printHelpOn(System.out);
      System.exit(-1);
    }

    // Logger configuration: log to the console
    BasicConfigurator.configure();
    LOG.setLevel(Level.DEBUG);
    LOG.debug("debug logging on");

    Configuration conf = new PropertiesConfiguration();

    if (options.has("c")) {
      String configFile = (String) options.valueOf("c");
      try {
        conf = new PropertiesConfiguration(configFile);
      } catch (ConfigurationException e) {}
    }
    // Start backend server
    ProtoBackend protoBackend = new ProtoBackend();
    BackendService.Processor<BackendService.Iface> processor =
        new BackendService.Processor<BackendService.Iface>(protoBackend);

    int listenPort = conf.getInt("listen_port", DEFAULT_LISTEN_PORT);
    NM_PORT = conf.getInt("node_monitor_port", NodeMonitorThrift.DEFAULT_NM_THRIFT_PORT);
    TServers.launchThreadedThriftServer(listenPort, THRIFT_WORKER_THREADS, processor);
    protoBackend.initialize(listenPort);
  }
}
TOP

Related Classes of edu.berkeley.sparrow.examples.ProtoBackend

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.