/* 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.faban.driver.engine; import com.sun.faban.driver.ConfigurationException; import com.sun.faban.driver.DefinitionException; import com.sun.faban.driver.OperationSequence; import com.sun.faban.driver.util.Random; import org.w3c.dom.Element; import java.util.HashMap; import java.util.Iterator; import java.lang.annotation.Annotation; /** * The implementation of the flat sequence mix annotation. * * * This mix allows you to specify a set of sequences of operations. For * example, a workload may define two scenarios: shopping and browsing. * The shopping operation itself is made up of three ordered operations (login, * buy, home) and the browsing operation is made up of two operations * (search, home). Say that 75% of the workload is browsing and 25% of * the workload is shopping. Such a mix of operations would be represented * like this: * <br> * <pre> @FlatSequenceMix ( sequences = { @OperationSequence({"login", "buy", "home" }), @OperationSequence({"search", "home" }) }, mix = { 25, 75 }, deviation = 2 )</pre> * The resulting metric will be the number of the 4 operations (login, buy, * search, home) in the time defined for each operation. The driver will * expect 45.75% of the operations to be home (50% of 75% of the operations * from browsing and 33% of 25% of the operations from shopping), 37.5% to * be search, and 8.25% each for login and buy. * * @author Scott Oaks */ public class FlatSequenceMix extends Mix { private static final long serialVersionUID = 1L; private String[] operationNames; private int[][] operationSequences; double[] mix; double[] normalizedMix; /** * Initializes this mix according to the annotation. * * @param driverClass The driver class annotating this mix * @param a The annotation * @throws com.sun.faban.driver.DefinitionException * If there is an error in the annotation */ public void init(Class<?> driverClass, Annotation a) throws DefinitionException { com.sun.faban.driver.FlatSequenceMix fsMix = (com.sun.faban.driver.FlatSequenceMix) a; OperationSequence[] seq = fsMix.sequences(); mix = fsMix.mix(); init(seq); operations = BenchmarkDefinition.getOperations( driverClass, operations()); deviation = fsMix.deviation(); validate(); } /** * Initialized the mix based on the defined sequences from the driver * annotations. * @param seq An array of {@link OperationSequence} */ public void init(OperationSequence[] seq) { // We're assuming mix[] adds to 100%, which is okay because // validate() told us that. HashMap<String, Double> set = new HashMap<String, Double>(); // Put all the operations into a map. For each operation, // calculate it's percentage in the particular sequence and // multiply by the percentage of the sequence in the mix. That's // the total expected for that operation. for (int i = 0; i < seq.length; i++) { String[] s = seq[i].value(); for (int j = 0; j < s.length; j++) { double factor = 1. / s.length; factor *= (mix[i] / 100.); Double d = set.get(s[j]); if (d == null) { d = new Double(factor); } else { d = new Double(d.doubleValue() + factor); } set.put(s[j], d); } } // Now we can calculate: // 1) The entire set of URLs and store them in operationNames // 2) The expected mix of those URLs based on how many times // they appeared. Iterator<String> it = set.keySet().iterator(); operationNames = new String[set.size()]; normalizedMix = new double[set.size()]; for (int i = 0; i < normalizedMix.length; i++) { String s = it.next(); operationNames[i] = s; normalizedMix[i] = set.get(s).doubleValue(); } // Finally, we calculate a map for the selector. The selector // will randomally pick an operationalSequence[x] and then // cycle through the array of URLs in that sequence. // // Pretty badly performing logic, but it's only done once at // startup, and it's easier than keeping another map. operationSequences = new int[seq.length][]; for (int i = 0; i < seq.length; i++) { String[] s = seq[i].value(); operationSequences[i] = new int[s.length]; for (int j = 0; j < s.length; j++) { operationSequences[i][j] = -1; for (int k = 0; k < operationNames.length; k++) { if (s[j].equals(operationNames[k])) { operationSequences[i][j] = k; break; } } } } } /** * Returns an array of the operation names used in this FlatMix. * @return operations The names of the operations */ public String[] operations() { return operationNames; } /** * The clone operation for this object is a deep copy. * * @return a clone of this instance. * @see Cloneable */ @Override public Object clone() { FlatSequenceMix clone = (FlatSequenceMix) super.clone(); if (mix != null) { clone.mix = new double[mix.length]; System.arraycopy(mix, 0, clone.mix, 0, mix.length); } else { clone.mix = null; } if (operationNames != null) { clone.operationNames = new String[operationNames.length]; System.arraycopy(operationNames, 0, clone.operationNames, 0, operationNames.length); } else { clone.operationNames = null; } if (operationSequences != null) { clone.operationSequences = new int[operationSequences.length][]; for (int i = 0; i < operationSequences.length; i++) { clone.operationSequences[i] = new int[operationSequences[i].length]; System.arraycopy(operationSequences[i], 0, clone.operationSequences[i], 0, operationSequences[i].length); } } else { clone.operationSequences = null; } return clone; } /** * Configures the flat mix based on the ratios given in the * configuration file. * @param driverConfigNode The DOM node holding the driver config * @throws ConfigurationException If the configuration is invalid * for the mix */ public void configure(Element driverConfigNode) throws ConfigurationException { // noop } /** * Validates the mix spec in the benchmark definitions to ensure all * the rows and columns are valid. * @throws com.sun.faban.driver.DefinitionException Found missing or * invalid row/column */ public void validate() throws DefinitionException { // noop } /** * Normalizes the mix so each row will have a sum of 1.0d. This is * important as we do not require the spec input to sum to this * amount but the selector (doMenu) will base the random number * generator to 1. */ public void normalize() { // if (logger.isLoggable(Level.FINEST)) getLogger().finest("normalize - before\n" + toString()); double rowTotal = 0d; for (int i = 0; i < mix.length; i++) { rowTotal += mix[i]; } for (int i = 0; i < mix.length; i++) { mix[i] /= rowTotal; } // if (logger.isLoggable(Level.FINEST)) getLogger().finest("normalize - after\n" + toString()); } /** * Returns the flat mix representation of this mix. This is used for * consistent reporting. * * @return The flat mix representation. */ public FlatMix flatMix() { FlatMix fm = new FlatMix(); fm.operations = operations; fm.deviation = deviation; fm.mix = normalizedMix; return fm; } /** * Provides a string representation of this FlatSequenceMix. * @return The string representation * @see java.lang.Object#toString() */ @Override public String toString() { StringBuilder buffer = new StringBuilder(); buffer.append("FlatSequenceMix\n"); buffer.append("operations: "); buffer.append(operations[0].name); for (int i = 1; i < operations.length; i++) { buffer.append(", "); buffer.append(operations[i].name); } buffer.append("\nmix: "); buffer.append(mix[0]); for (int i = 1; i < mix.length; i++) { buffer.append(", "); buffer.append(mix[i]); } buffer.append("\ndeviation :"); buffer.append(deviation); buffer.append('\n'); return buffer.toString(); } /** * Obtains the per-thread and per-driver instance selector. * * @param random The per-thread random value generator * @return The selector to be used by the driver */ public Selector selector(Random random) { return new Selector(random, mix, operationSequences); } /** * The selector implementation for the FlatSequenceMix. */ public static class Selector extends Mix.Selector { private Random random; private double[] selectMix; private int curSequence; private int curIndex; private int[][] operationSequences; Selector(Random random, double[] mix, int[][] operationSequences) { this.operationSequences = operationSequences; this.random = random; selectMix = new double[mix.length]; selectMix[0] = mix[0]; for (int i = 1; i < selectMix.length; i++) { selectMix[i] = mix[i] + selectMix[i - 1]; } // Resets the selector to starting position. reset(); } /** * The select method selects the operation to run next. * * @return The operation index selected to run next */ public int select() { if (curIndex == operationSequences[curSequence].length) { curIndex = 0; double val = random.drandom(0, 1); for (curSequence = 0; curSequence < selectMix.length; curSequence++) { if (val <= selectMix[curSequence]) { break; } } } return operationSequences[curSequence][curIndex++]; } /** * Resets the selector's state to start at the first op, * if applicable. */ public void reset() { curSequence = 0; curIndex = operationSequences[0].length; } } }