diff options
Diffstat (limited to 'qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/util/MathUtils.java')
-rw-r--r-- | qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/util/MathUtils.java | 428 |
1 files changed, 428 insertions, 0 deletions
diff --git a/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/util/MathUtils.java b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/util/MathUtils.java new file mode 100644 index 0000000000..7c803294f4 --- /dev/null +++ b/qpid/java/junit-toolkit/src/main/org/apache/qpid/junit/extensions/util/MathUtils.java @@ -0,0 +1,428 @@ +/* + * + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.qpid.junit.extensions.util; + +import java.util.ArrayList; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Mathematical support methods for the toolkit. Caculating averages, variances, min/max for test latencies and + * generating linear/exponential sequences for test size/concurrency ramping up. + * + * <p/>The sequence specifications are of the form [lowest(, ...)(, highest)](,sample=s)(,exp), where round brackets + * enclose optional values. Using this pattern form it is possible to specify a single value, a range of values divided + * into s samples, a range of values divided into s samples but distributed exponentially, or a fixed set of samples. + * + * <p/>The duration arguments are of the form (dD)(hH)(mM)(sS), where round brackets enclose optional values. At least + * one of the optional values must be present. + * + * <p/><table id="crc"><caption>CRC Card</caption> + * <tr><th> Responsibilities <th> Collaborations + * <tr><td> Generate a sequene of integers from a sequence specification. + * <tr><td> Parse an encoded duration into milliseconds. + * </table> + * + * @author Rupert Smith + */ +public class MathUtils +{ + /** Used for debugging. */ + // private static final Logger log = Logger.getLogger(MathUtils.class); + + /** The sequence defintion matching regular expression. */ + public static final String SEQUENCE_REGEXP = "^(\\[[0-9:]+\\])(:samples=[0-9]+)?(:exp)?$"; + + /** The regular expression that matches sequence definitions. */ + private static final Pattern SEQUENCE_PATTERN = Pattern.compile(SEQUENCE_REGEXP); + + /** The duration definition matching regular expression. */ + public static final String DURATION_REGEXP = "^(\\d+D)?(\\d+H)?(\\d+M)?(\\d+S)?$"; + + /** The regular expression that matches the duration expression. */ + public static final Pattern DURATION_PATTERN = Pattern.compile(DURATION_REGEXP); + + /** For matching name=value pairs. */ + public static final String NAME_VALUE_REGEXP = "^\\w+=\\w+$"; + + /** For matching name=[value1: value2: ...] variations. */ + public static final String NAME_VALUE_VARIATION_REGEXP = "^\\w+=\\[[\\w:]+\\]$"; + + /** For matching name=[n: ... :m](:sample=s)(:exp) sequences. */ + public static final String NAME_VALUE_SEQUENCE_REGEXP = "^\\w+=(\\[[0-9:]+\\])(:samples=[0-9]+)?(:exp)?$"; + + /** The regular expression that matches name=value pairs and variations. */ + public static final Pattern NAME_VALUE_PATTERN = + Pattern.compile("(" + NAME_VALUE_REGEXP + ")|(" + NAME_VALUE_VARIATION_REGEXP + ")|(" + NAME_VALUE_SEQUENCE_REGEXP + + ")"); + + /** + * Runs a quick test of the sequence generation methods to confirm that they work as expected. + * + * @param args The command line parameters. + */ + public static void main(String[] args) + { + // Use the command line parser to evaluate the command line. + CommandLineParser commandLine = + new CommandLineParser( + new String[][] + { + { "s", "The sequence definition.", "[m:...:n](:sample=s)(:exp)", "true", MathUtils.SEQUENCE_REGEXP }, + { "d", "The duration definition.", "dDhHmMsS", "false", MathUtils.DURATION_REGEXP } + }); + + // Capture the command line arguments or display errors and correct usage and then exit. + ParsedProperties options = null; + + try + { + options = new ParsedProperties(commandLine.parseCommandLine(args)); + } + catch (IllegalArgumentException e) + { + System.out.println(commandLine.getErrors()); + System.out.println(commandLine.getUsage()); + System.exit(-1); + } + + // Extract the command line options. + String sequence = options.getProperty("s"); + String durationString = options.getProperty("d"); + + System.out.println("Sequence is: " + printArray(parseSequence(sequence))); + + if (durationString != null) + { + System.out.println("Duration is: " + parseDuration(durationString)); + } + } + + /** + * Given a start and end and a number of steps this method generates a sequence of evenly spaced integer + * values, starting at the start (inclusive) and finishing at the end (inclusive) with the specified number + * of values in the sequence. The sequence returned may contain less than the specified number where the integer + * range between start and end is too small to contain that many. + * + * <p/>As the results are integers, they will not be perfectly evenly spaced but a best-fit. + * + * @param start The sequence start. + * @param end The sequence end. + * @param steps The number of steps. + * + * @return The sequence. + */ + public static int[] generateSequence(int start, int end, int steps) + { + // Check that there are at least two steps. + if (steps < 2) + { + throw new IllegalArgumentException("There must be at least 2 steps."); + } + + ArrayList<Integer> result = new ArrayList<Integer>(); + + // Calculate the sequence using floating point, then round into the results. + double fStart = start; + double fEnd = end; + double fCurrent = start; + + for (int i = 0; i < steps; i++) + { + fCurrent = (((fEnd - fStart) / (steps - 1)) * i) + fStart; + + roundAndAdd(result, fCurrent); + } + + // Return the results after converting to a primitive array. + return intListToPrimitiveArray(result); + } + + /** + * Given a start and end and a number of steps this method generates a sequence of expontentially spaced integer + * values, starting at the start (inclusive) and finishing at the end (inclusive) with the specified number + * of values in the sequence. An exponentially spaced sequence is one where the ratio between any two consecutive + * numbers in the sequence remains constant. The sequence returned may contain less than the specified number where + * the difference between two consecutive values is too small (this is more likely at the start of the sequence, + * where the values are closer together). + * + * <p/>As the results are integers, they will not be perfectly exponentially spaced but a best-fit. + * + * @param start The sequence start. + * @param end The sequence end. + * @param steps The number of steps. + * + * @return The sequence. + */ + public static int[] generateExpSequence(int start, int end, int steps) + { + // Check that there are at least two steps. + if (steps < 2) + { + throw new IllegalArgumentException("There must be at least 2 steps."); + } + + ArrayList<Integer> result = new ArrayList<Integer>(); + + // Calculate the sequence using floating point, then round into the results. + double fStart = start; + double fEnd = end; + // float fCurrent = start; + double diff = fEnd - fStart; + double factor = java.lang.Math.pow(diff, (1.0f / (steps - 1))); + + for (int i = 0; i < steps; i++) + { + // This is a cheat to get the end exactly on and lose the accumulated rounding error. + if (i == (steps - 1)) + { + result.add(end); + } + else + { + roundAndAdd(result, fStart - 1.0f + java.lang.Math.pow(factor, i)); + } + } + + // Return the results after converting to a primitive array. + return intListToPrimitiveArray(result); + } + + /** + * Parses a string defintion of a sequence into an int array containing the sequence. The definition will conform + * to the regular expression: "^(\[[0-9,]+\])(,samples=[0-9]+)?(,exp)?$". This splits it into three parts, + * an array of integers, the optional sample count and the optional exponential flag. + * + * @param sequenceDef The sequence definition. + * + * @return The sequence as a fully expanded int array. + */ + public static int[] parseSequence(String sequenceDef) + { + // Match the sequence definition against the regular expression for sequences. + Matcher matcher = SEQUENCE_PATTERN.matcher(sequenceDef); + + // Check that the argument is of the right format accepted by this method. + if (!matcher.matches()) + { + throw new IllegalArgumentException("The sequence definition is not in the correct format."); + } + + // Get the total number of matching groups to see if either of the optional samples or exponential flag + // goups were set. + int numGroups = matcher.groupCount(); + + // Split the array of integers on commas. + String intArrayString = matcher.group(1); + + String[] intSplits = intArrayString.split("[:\\[\\]]"); + + int[] sequence = new int[intSplits.length - 1]; + + for (int i = 1; i < intSplits.length; i++) + { + sequence[i - 1] = Integer.parseInt(intSplits[i]); + } + + // Check for the optional samples count. + int samples = 0; + + if ((numGroups > 1) && (matcher.group(2) != null)) + { + String samplesGroup = matcher.group(2); + + String samplesString = samplesGroup.substring(",samples=".length()); + samples = Integer.parseInt(samplesString); + } + + // Check for the optional exponential flag. + boolean expFlag = false; + + if ((numGroups > 2) && (matcher.group(3) != null)) + { + expFlag = true; + } + + // If there is a sample count and 2 or more sequence values defined, then generate the sequence from the first + // and last sequence values. + if ((samples != 0) && (sequence.length >= 2)) + { + int start = sequence[0]; + int end = sequence[sequence.length - 1]; + + if (!expFlag) + { + sequence = generateSequence(start, end, samples); + } + else + { + sequence = generateExpSequence(start, end, samples); + } + } + + return sequence; + } + + /** + * Parses a duration defined as a string, giving a duration in days, hours, minutes and seconds into a number + * of milliseconds equal to that duration. + * + * @param duration The duration definition string. + * + * @return The duration in millliseconds. + */ + public static long parseDuration(String duration) + { + // Match the duration against the regular expression. + Matcher matcher = DURATION_PATTERN.matcher(duration); + + // Check that the argument is of the right format accepted by this method. + if (!matcher.matches()) + { + throw new IllegalArgumentException("The duration definition is not in the correct format."); + } + + // This accumulates the duration. + long result = 0; + + int numGroups = matcher.groupCount(); + + // Extract the days. + if (numGroups >= 1) + { + String daysString = matcher.group(1); + result += + (daysString == null) + ? 0 : (Long.parseLong(daysString.substring(0, daysString.length() - 1)) * 24 * 60 * 60 * 1000); + } + + // Extract the hours. + if (numGroups >= 2) + { + String hoursString = matcher.group(2); + result += + (hoursString == null) ? 0 + : (Long.parseLong(hoursString.substring(0, hoursString.length() - 1)) * 60 * 60 * 1000); + } + + // Extract the minutes. + if (numGroups >= 3) + { + String minutesString = matcher.group(3); + result += + (minutesString == null) + ? 0 : (Long.parseLong(minutesString.substring(0, minutesString.length() - 1)) * 60 * 1000); + } + + // Extract the seconds. + if (numGroups >= 4) + { + String secondsString = matcher.group(4); + result += + (secondsString == null) ? 0 : (Long.parseLong(secondsString.substring(0, secondsString.length() - 1)) * 1000); + } + + return result; + } + + /** + * Pretty prints an array of ints as a string. + * + * @param array The array to pretty print. + * + * @return The pretty printed string. + */ + public static String printArray(int[] array) + { + String result = "["; + for (int i = 0; i < array.length; i++) + { + result += array[i]; + result += (i < (array.length - 1)) ? ", " : ""; + } + + result += "]"; + + return result; + } + + /** + * Returns the maximum value in an array of integers. + * + * @param values The array to find the amx in. + * + * @return The max value. + */ + public static int maxInArray(int[] values) + { + if ((values == null) || (values.length == 0)) + { + throw new IllegalArgumentException("Cannot find the max of a null or empty array."); + } + + int max = values[0]; + + for (int value : values) + { + max = (max < value) ? value : max; + } + + return max; + } + + /** + * The #toArray methods of collections cannot be used with primitive arrays. This loops over and array list + * of Integers and outputs and array of int. + * + * @param result The array of Integers to convert. + * + * @return An array of int. + */ + private static int[] intListToPrimitiveArray(ArrayList<Integer> result) + { + int[] resultArray = new int[result.size()]; + int index = 0; + for (int r : result) + { + resultArray[index] = result.get(index); + index++; + } + + return resultArray; + } + + /** + * Rounds the specified floating point value to the nearest integer and adds it to the specified list of + * integers, provided it is not already in the list. + * + * @param result The list of integers to add to. + * @param value The new candidate to round and add to the list. + */ + private static void roundAndAdd(ArrayList<Integer> result, double value) + { + int roundedValue = (int) Math.round(value); + + if (!result.contains(roundedValue)) + { + result.add(roundedValue); + } + } +} |