content: /* The contents of this file are subject to the terms
   * of the Common Development and Distribution License
   * (the License). You may not use this file except in
   * compliance with the License.
   *
   * You can obtain a copy of the License at
   * http://www.sun.com/cddl/cddl.html or
   * install_dir/legal/LICENSE
   * See the License for the specific language governing
   * permission and limitations under the License.
   *
   * When distributing Covered Code, include this CDDL
   * Header Notice in each file and include the License file
   * at install_dir/legal/LICENSE.
   * If applicable, add the following below the CDDL Header,
   * with the fields enclosed by brackets [] replaced by
   * your own identifying information:
   * "Portions Copyrighted [year] [name of copyright owner]"
   *
   * $Id$
   *
   * Copyright 2005-2009 Sun Microsystems Inc. All Rights Reserved
   */
  package com.sun.specweb2005;
  
  import com.sun.faban.common.Command;
  import com.sun.faban.common.CommandHandle;
  import com.sun.faban.common.NameValuePair;
  import com.sun.faban.harness.*;
  
  import javax.xml.transform.Transformer;
  import javax.xml.transform.TransformerFactory;
  import javax.xml.transform.stream.StreamResult;
  import javax.xml.transform.stream.StreamSource;
  import java.io.*;
  import java.util.Calendar;
  import java.util.List;
  import java.util.logging.Level;
  import java.util.logging.Logger;
  
  import static com.sun.faban.harness.RunContext.*;
  import static com.sun.faban.harness.util.FileHelper.copyFile;
  
  
  /**
   * Harness hook for SPECweb2005 drives the process for executing
   * SPECweb2005.
   *
   * @author Sreekanth Setty
   */
  public class SpecWeb2005Benchmark {
  
      static Logger logger = Logger.getLogger(
                                          SpecWeb2005Benchmark.class.getName());
      String clientJar;
      String testType;
      String runDir;
      String runID;
      ParamRepository par;
      CommandHandle handle;
      private Calendar startTime;
      List<NameValuePair<Integer>> hostsPorts;
  
      /**
       * Allows benchmark to validate the configuration file. Note that no
       * execution facility is available during validation. This method is just
       * for validation and modifications of the run configuration.
       *
       * @throws Exception if any error occurred.
       * @see com.sun.faban.harness.RunContext#exec(com.sun.faban.common.Command)
       */
      @Validate public void validate() throws Exception {
  
          // add runControl parameters due to faban requirement
          par = getParamRepository();
  
          int rampUp = Integer.parseInt(par.getParameter(
                                          "fa:runConfig/threadRampupSeconds"));
          rampUp += Integer.parseInt(par.getParameter(
                                          "fa:runConfig/warmupSeconds"));
  
          // Faban uses some standard names such as rampUp
          par.setParameter("fa:runConfig/fa:runControl/fa:rampUp",
                                                  String.valueOf(rampUp));
  
  
          // Note that runConfig/clients is used during the generation
          // of Test.config file. And runConfig/hostConfig/host is used for
          // starting the faban processes
          hostsPorts = par.getHostPorts(
                                  "fa:runConfig/fa:hostConfig/fa:hostPorts");
          // Do all translations
          runDir = getOutDir();
          runID = getRunId();
          testType = par.getParameter("fa:runConfig/testtype");
          clientJar = par.getParameter("fa:runConfig/clientDir");
  
          // Translate run.xml into Test.config.
          StreamSource stylesheet = new StreamSource(getBenchmarkDir() +
                  "META-INF" + File.separator + "run.xsl");
          StreamSource src = new StreamSource(getParamFile());
          StreamResult result = new StreamResult(
                                    new File(runDir, "Test.config"));
          Transformer t = TransformerFactory.newInstance().
                              newTransformer(stylesheet);
          t.setParameter("outputDir", runDir);
          t.transform(src, result);
  
          // Translate run.xml into appropriate
          // (banking/ecommerce/support) config file.
          stylesheet = new StreamSource(getBenchmarkDir() + "META-INF" +
                  File.separator + testType + ".xsl");
          result = new StreamResult(
                                    new File(runDir, testType + ".config"));
          t = TransformerFactory.newInstance().
                              newTransformer(stylesheet);
          t.setParameter("outputDir", runDir);
          t.transform(src, result);
  
          // Translate run.xml into Testbed.config
          stylesheet = new StreamSource(getBenchmarkDir() + "META-INF" +
                  File.separator + "testbed.xsl");
          result = new StreamResult(new File(runDir, "Testbed.config"));
          t = TransformerFactory.newInstance().
                              newTransformer(stylesheet);
          t.setParameter("outputDir", runDir);
          t.transform(src, result);
      }
  
      private void start_clients() throws Exception {
          // The run's output dir is always run 1 under the assigned output dir.
          // We just need to move the files down after the run (or do we need to?)
  
          String javaOptions = par.getParameter("fh:jvmConfig/fh:jvmOptions");
          // replacing the GC option with a blank space
          String javaOptionsNoGC = javaOptions.replaceFirst("-Xloggc:\\S+", " ");
  
          cleanOldFiles(par.getTokenizedValue(
                                      "fa:runConfig/fa:hostConfig/fa:host"));
  
          for (NameValuePair hostPort : hostsPorts) {
              String tmp = getTmpDir(hostPort.name);
              String out = tmp + "out." + runID;
              String errors = tmp + "errors." + runID;
              String gc = tmp + "gc." + runID;
  
              if (hostPort.value != null) {
                  out += "." + hostPort.value;
                  errors += "." + hostPort.value;
                  gc += "." + hostPort.value;
              }
  
              // command to start to a client driver process
              String cmd = "java " +  javaOptionsNoGC +   " -Xloggc:" + gc +
                              " -classpath " + clientJar + " specwebclient ";
              if (hostPort.value != null)
                  cmd += " -p " +  hostPort.value;
  
              logger.info("Starting the client on " + hostPort.name + ": " + cmd);
              Command client = new Command(cmd);
              client.setSynchronous(false);
              client.setOutputFile(Command.STDOUT, out);
              client.setOutputFile(Command.STDERR, errors);
              // Using capture will enable logging to the files, in 8K chunks.
              client.setStreamHandling(Command.STDOUT, Command.CAPTURE);
              client.setStreamHandling(Command.STDERR, Command.CAPTURE);
              // client.setWorkingDirectory("/export/home/w2005");
  
              exec(hostPort.name, client);
          }
      }
  
      // The code looks easier this way passing the code block to execute on
      // a remote host. But semantically it can be more complicated. If the
      // object passed has an enclosing object, the enclosing object and all it's
      // state must be serializable. To create a static anonymous inner class
      // not to serialize the enclosing object, we need to do this in a static
      // method. Also, although it looks like all the enclosing class'
      // variables are accessible syntactically, their behavior is often
      // undefined when running remotely. So we need to make sure that this
      // anonymous inner class does not make such assumptions. Access to the
      // RunContext gives undefined results, for example.
      private static void cleanOldFiles(String[] hosts) {
          // Clean the result/error/gc files and restart the client driver
          // processes. The processes will be killed at the end of the run.
          for (String hostName : hosts) {
              final String tmp = getTmpDir(hostName);
              try {
                  exec(hostName, new RemoteCallable() {
                      public Serializable call() throws Exception {
                          // Now running on the target host.
                          // The RunContext is virtually non-existent here
                          // RunContext methods have undefined behavior.
                          logger.info("Cleaning the temporary files in " + tmp);
                          // Now running on the target host, tmpdir is local.
                          File tmpDir = new File(tmp);
                          File[] fileList = tmpDir.listFiles();
                          for (File f : fileList) {
                             if (f.getName().startsWith("out") ||
                                 f.getName().startsWith("err") ||
                                 f.getName().startsWith("gc"))
                                  if (f.isFile() && !f.delete())
                                      logger.info("Could not delete file: " +
                                                      tmp + f.getName());
                          }
                          return null;
                      }
                  });
              } catch (Exception e) {
                  logger.log(Level.WARNING, "Exception deleting files", e);
              }
          }
      }
  
      /**
       * This method is responsible for starting the benchmark run.
       * @throws Exception Error starting the run
       */
      @StartRun public void start() throws Exception {
          // The run's output dir is always run 1 under the assigned output dir.
          // We just need to move the files down after the run (or do we need to?)
  
          start_clients();
          // run >>
          // 1. start the run.
  
          // We need to add the jfreechart libs to the prime client.
          StringBuilder classpath = new StringBuilder();
          File clientDir = new File(clientJar).getParentFile();
          File[] files = clientDir.listFiles();
          for (File file : files) {
              String fileName = file.getName();
              if (fileName.endsWith(".jar")) {
                  if (fileName.startsWith("jcommon") ||
                          fileName.startsWith("jfreechart")) {
                      String absolutePath = file.getAbsolutePath();
                      classpath.append(absolutePath).append(
                              File.pathSeparatorChar);
                  }
              }
          }
          classpath.append(clientJar);
  
          String cmd = "java -server -Xmx800m -Xms800m -classpath " +
                       classpath + "  specweb";
          logger.info("Starting the Master: " + cmd);
          Command c = new Command(cmd);
  
          // 2. Trickle the log.
          c.setSynchronous(false);
          c.setOutputFile(Command.STDOUT, "result.txt");
          // Using trickle will enable the Server to see the data
          c.setStreamHandling(Command.STDOUT, Command.TRICKLE_LOG);
          c.setStreamHandling(Command.STDERR, Command.TRICKLE_LOG);
          c.setWorkingDirectory(runDir);
  
          startTime = Calendar.getInstance();
          handle = exec(c);
      }
  
      /**
       * This method is responsible for waiting for all commands started and
       * run all postprocessing needed.
       *
       * @throws Exception if any error occurred.
       */
      @EndRun public void end() throws Exception {
  
          // 4. Wait for run finish.
          handle.waitFor();
  
          Calendar endTime = Calendar.getInstance();
  
          File resultsDir = new File(runDir, "results");
          if (!resultsDir.isDirectory())
              throw new Exception("The results directory not found!");
  
          String resHtml = null;
          String resTxt = null;
          String[] resultFiles = resultsDir.list();
          for (String resultFile : resultFiles) {
              if (resultFile.indexOf("html") >= 0)
                  resHtml = resultFile;
              else if (resultFile.indexOf("txt") >= 0)
                  resTxt = resultFile;
          }
          if (resHtml == null)
              throw new IOException("SPECweb2005 output (html) file not found");
  
          if (resTxt == null)
              throw new IOException("SPECweb2005 output (txt) file not found");
  
          File resultHtml = new File(resultsDir, resHtml);
  
          copyFile(resultHtml.getAbsolutePath(),
                  runDir + "SPECWeb-result.html", false);
  
  
          BufferedReader reader = new BufferedReader(new FileReader(
                                      new File(resultsDir, resTxt)));
          logger.fine("Text file: " + resultsDir + resTxt);
          // String sessions = par.getParameter("runConfig/sessions");
          Writer writer = new FileWriter(runDir + "summary.xml");
          SummaryParser parser =
                  new SummaryParser(getRunId(), startTime, endTime, logger);
          parser.convert(reader, writer);
          writer.close();
          reader.close();
      }
  }
  