All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.unboundid.util.RateAdjustor Maven / Gradle / Ivy

Go to download

The UnboundID LDAP SDK for Java is a fast, comprehensive, and easy-to-use Java API for communicating with LDAP directory servers and performing related tasks like reading and writing LDIF, encoding and decoding data using base64 and ASN.1 BER, and performing secure communication. This package contains the Standard Edition of the LDAP SDK, which is a complete, general-purpose library for communicating with LDAPv3 directory servers.

There is a newer version: 7.0.1
Show newest version
/*
 * Copyright 2014-2023 Ping Identity Corporation
 * All Rights Reserved.
 */
/*
 * Copyright 2014-2023 Ping Identity Corporation
 *
 * 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.
 */
/*
 * Copyright (C) 2014-2023 Ping Identity Corporation
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License (GPLv2 only)
 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see .
 */
package com.unboundid.util;



import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;

import com.unboundid.util.args.ArgumentException;
import com.unboundid.util.args.DurationArgument;

import static com.unboundid.util.UtilityMessages.*;



/**
 * This class allows a FixedRateBarrier to change dynamically.  The rate changes
 * are governed by lines read from a {@code Reader} (typically backed by a
 * file). The input starts with a header that provides some global options and
 * then has a list of lines, where each line contains a single rate per second,
 * a comma, and a duration to maintain that rate.  Rates are specified as an
 * absolute rate per second or as a rate relative to the base rate per second.
 * The duration is an integer followed by a time unit (ms=milliseconds,
 * s=seconds, m=minutes, h=hours, and d=days).
 * 

* The following simple example will run at a target rate of 1000 per second * for one minute, and then 10000 per second for 10 seconds. *
 *   # format=rate-duration
 *   1000,1m
 *   10000,10s
 * 
*
* The following example has a default duration of one minute, and will repeat * the two intervals until this RateAdjustor is shut down. The first interval * is run for the default of 1 minute at two and half times the base rate, and * then run for 10 seconds at 10000 per second. *
 *   # format=rate-duration
 *   # default-duration=1m
 *   # repeat=true
 *   2.5X
 *   10000,10s
 * 
* A {@code RateAdjustor} is a daemon thread. It is necessary to call the * {@code start()} method to start the thread and begin the rate changes. * Once this finished processing the rates, the thread will complete. * It can be stopped prematurely by calling {@code shutDown()}. *

* The header can contain the following options: *
    *
  • {@code format} (required): This must currently have the value * {@code rate-duration}.
  • *
  • {@code default-duration} (optional): This can specify a default * duration for intervals that do not include a duration. The format * is an integer followed by a time unit as described above.
  • *
  • {@code repeat} (optional): If this has a value of {@code true}, then * the rates in the input will be repeated until {@code shutDown()} is * called.
  • *
*/ @ThreadSafety(level = ThreadSafetyLevel.MOSTLY_THREADSAFE) public final class RateAdjustor extends Thread { /** * This starts a comment in the input. */ public static final char COMMENT_START = '#'; /** * The text that must appear on a line by itself in order to denote that the * end of the file header has been reached. */ @NotNull public static final String END_HEADER_TEXT = "END HEADER"; /** * The header key that represents the default duration. */ @NotNull public static final String DEFAULT_DURATION_KEY = "default-duration"; /** * The header key that represents the format of the file. */ @NotNull public static final String FORMAT_KEY = "format"; /** * The value of the format key that represents a list of rates and durations * within the input file. */ @NotNull public static final String FORMAT_VALUE_RATE_DURATION = "rate-and-duration"; /** * A list of all formats that we support. */ @NotNull public static final List FORMATS = Collections.singletonList(FORMAT_VALUE_RATE_DURATION); /** * The header key that represents whether the input should be repeated. */ @NotNull public static final String REPEAT_KEY = "repeat"; /** * A list of all header keys that we support. */ @NotNull public static final List KEYS = Arrays.asList(DEFAULT_DURATION_KEY, FORMAT_KEY, REPEAT_KEY); // Other headers to consider: // * rate-multiplier, so you can easily proportionally increase or decrease // every target rate without changing all the target rates directly. // * duration-multiplier, so you can easily proportionally increase or // decrease the length of time to spend at target rates. // * rate-change-behavior, so you can specify the behavior that should be // exhibited when transitioning from one rate to another (e.g., instant // jump, linear acceleration, sine-based acceleration, etc.). // * jitter, so we can introduce some amount of random jitter in the target // rate (in which the actual target rate may be frequently adjusted to be // slightly higher or lower than the designated target rate). // * spike, so we can introduce periodic, substantial increases in the target // rate. // The barrier whose rate is adjusted. @NotNull private final FixedRateBarrier barrier; // A list of rates per second and the number of milliseconds that the // specified rate should be maintained. @NotNull private final List> ratesAndDurations; // If this is true, then the ratesAndDurations will be repeated until this is // shut down. private final boolean repeat; // Set to true when this should shut down. private volatile boolean shutDown = false; // This is used to make sure we set the initial rate before start() returns. @NotNull private final CountDownLatch initialRateSetLatch = new CountDownLatch(1); // This allows us to interrupt when we are sleeping. @NotNull private final WakeableSleeper sleeper = new WakeableSleeper(); /** * Returns a new RateAdjustor with the specified parameters. See the * class-level javadoc for more information. * * @param barrier The barrier to update based on the specified * rates. * @param baseRatePerSecond The baseline rate per second, or {@code null} * if none was specified. * @param rates A file containing a list of rates and durations * as described in the class-level javadoc. * * @return A new RateAdjustor constructed from the specified parameters. * * @throws IOException If there is a problem reading from * the rates Reader. * @throws IllegalArgumentException If there is a problem with the rates * input. */ @NotNull() public static RateAdjustor newInstance( @NotNull final FixedRateBarrier barrier, @Nullable final Integer baseRatePerSecond, @NotNull final File rates) throws IOException, IllegalArgumentException { final Reader reader = new FileReader(rates); return new RateAdjustor( barrier, (baseRatePerSecond == null) ? 0 : baseRatePerSecond, reader); } /** * Retrieves a string that may be used as the description of the argument that * specifies the path to a variable rate data file for use in conjunction with * this rate adjustor. * * @param genArgName The name of the argument that may be used to generate a * sample variable rate data file. * * @return A string that may be used as the description of the argument that * specifies the path to a variable rate data file for use in * conjunction with this rate adjustor. */ @Nullable() public static String getVariableRateDataArgumentDescription( @NotNull final String genArgName) { return INFO_RATE_ADJUSTOR_VARIABLE_RATE_DATA_ARG_DESCRIPTION.get( genArgName); } /** * Retrieves a string that may be used as the description of the argument that * generates a sample variable rate data file that serves as documentation of * the variable rate data format. * * @param dataFileArgName The name of the argument that specifies the path * to a file * * @return A string that may be used as the description of the argument that * generates a sample variable rate data file that serves as * documentation of the variable rate data format. */ @Nullable() public static String getGenerateSampleVariableRateFileDescription( @NotNull final String dataFileArgName) { return INFO_RATE_ADJUSTOR_GENERATE_SAMPLE_RATE_FILE_ARG_DESCRIPTION.get( dataFileArgName); } /** * Writes a sample variable write data file to the specified location. * * @param f The path to the file to be written. * * @throws IOException If a problem is encountered while writing to the * specified file. */ public static void writeSampleVariableRateFile(@NotNull final File f) throws IOException { final PrintWriter w = new PrintWriter(f); try { w.println("# This is an example variable rate data file. All blank " + "lines will be ignored."); w.println("# All lines starting with the '#' character are considered " + "comments and will"); w.println("# also be ignored."); w.println(); w.println("# The beginning of the file must be a header containing " + "properties pertaining"); w.println("# to the variable rate data. All headers must be in the " + "format 'name=value',"); w.println("# in which any spaces surrounding the equal sign will be " + "ignored."); w.println(); w.println("# The first header should be the 'format' header, which " + "specifies the format"); w.println("# for the variable rate data file. This header is " + "required. At present, the"); w.println("# only supported format is 'rate-and-duration', although " + "additional formats may"); w.println("# be added in the future."); w.println("format = rate-and-duration"); w.println(); w.println("# The optional 'default-duration' header may be used to " + "specify a duration that"); w.println("# will be used for any interval that does not explicitly " + "specify a duration."); w.println("# The duration must consist of a positive integer value " + "followed by a time"); w.println("# unit (with zero or more spaces separating the integer " + "value from the unit)."); w.println("# The supported time units are:"); w.println("#"); w.println("# - nanoseconds, nanosecond, nanos, nano, ns"); w.println("# - microseconds, microseconds, micros, micro, us"); w.println("# - milliseconds, millisecond, millis, milli, ms"); w.println("# - seconds, second, secs, sec, s"); w.println("# - minutes, minute, mins, min, m"); w.println("# - hours, hour, hrs, hr, h"); w.println("# - days, day, d"); w.println("#"); w.println("# If no 'default-duration' header is present, then every " + "data interval must"); w.println("# include an explicitly-specified duration."); w.println("default-duration = 10 seconds"); w.println(); w.println("# The optional 'repeat' header may be used to indicate how " + "the tool should"); w.println("# behave once the end of the variable rate data definitions " + "has been reached."); w.println("# If the 'repeat' header is present with a value of 'true', " + "then the tool will"); w.println("# operate in an endless loop, returning to the beginning of " + "the variable rate"); w.println("# definitions once the end has been reached. If the " + "'repeat' header is present"); w.println("# with a value of 'false', or if the 'repeat' header is " + "absent, then the tool"); w.println("# will exit after it has processed all of the variable " + "rate definitions."); w.println("repeat = true"); w.println(); w.println("# After all header properties have been specified, the end " + "of the header must"); w.println("# be signified with a line containing only the text 'END " + "HEADER'."); w.println("END HEADER"); w.println(); w.println(); w.println("# After the header is complete, the variable rate " + "definitions should be"); w.println("# provided. Each definition should be given on a line by " + "itself, and should"); w.println("# contain a target rate per second and an optional length " + "of time to maintain"); w.println("# that rate."); w.println("#"); w.println("# The target rate must always be present in a variable " + "rate definition. It may"); w.println("# be either a positive integer value that specifies the " + "absolute target rate"); w.println("# per second (e.g., a value of '1000' indicates a target " + "rate of 1000"); w.println("# operations per second), or it may be a floating-point " + "value followed by the"); w.println("# letter 'x' to indicate that it is a multiplier of the " + "value specified by the"); w.println("# '--ratePerSecond' argument (e.g., if the " + "'--ratePerSecond' argument is"); w.println("# present with a value of 1000, then a target rate value " + "of '0.75x' indicates a"); w.println("# target rate that is 75% of the '--ratePerSecond' value, " + "or 750 operations per"); w.println("# second). If the latter format is used, then the " + "'--ratePerSecond' argument"); w.println("# must be provided."); w.println("#"); w.println("# The duration may optionally be present in a variable " + "rate definition. If"); w.println("# present, it must be separated from the target rate by a " + "comma (and there may"); w.println("# be zero or more spaces on either side of the comma). " + "The duration must be in"); w.println("# the same format as specified in the description of the " + "'default-duration'"); w.println("# header above (i.e., a positive integer followed by a " + "time unit). If a"); w.println("# variable rate definition does not include a duration, " + "then the"); w.println("# 'default-duration' header must have been specified, and " + "that default duration"); w.println("# will be used for that variable rate definition."); w.println("#"); w.println("# The following variable rate definitions may be used to " + "stairstep the target"); w.println("# rate from 1000 operations per second to 10000 operations " + "per second, in"); w.println("# increments of 1000 operations per second, spending one " + "minute at each level."); w.println("# If the 'repeat' header is present with a value of 'true', " + "then the process"); w.println("# will start back over at 1000 operations per second after " + "completing one"); w.println("# minute at 10000 operations per second. Otherwise, the " + "tool will exit after"); w.println("# completing the 10000 operation-per-second interval."); w.println("1000, 1 minute"); w.println("2000, 1 minute"); w.println("3000, 1 minute"); w.println("4000, 1 minute"); w.println("5000, 1 minute"); w.println("6000, 1 minute"); w.println("7000, 1 minute"); w.println("8000, 1 minute"); w.println("9000, 1 minute"); w.println("10000, 1 minute"); w.println(); w.println(); w.println("# Additional sample rate definitions that represent common " + "load patterns are"); w.println("# provided below. Each of these patterns makes use of the " + "relative format for"); w.println("# the target rate and therefore require the " + "'--ratePerSecond' argument to"); w.println("# specify the target rate. These sample rate definitions " + "are commented out to"); w.println("# prevent them from being interpreted by default."); w.println(); w.println(); w.println("# Example: Square Rate"); w.println("#"); w.println("# This pattern starts with a rate of zero operations per " + "second, then"); w.println("# immediately jumps to a rate of 100% of the target rate. " + "A graph of the load"); w.println("# generated by repeating iterations of this pattern " + "represents a series of"); w.println("# squares that are alternately missing the top and bottom " + "edges."); w.println("#"); w.println("#0.00x"); w.println("#1.00x"); w.println(); w.println(); w.println("# Example: Stairstep Rate"); w.println("#"); w.println("# This pattern starts with a rate that is 10% of the target " + "rate, then jumps to"); w.println("# 20% of the target rate, then 30%, 40%, 50%, etc. until it " + "reaches 100% of the"); w.println("# target rate. A graph of the load generated by a single " + "iteration of this"); w.println("# pattern represents a series of stair steps."); w.println("#"); w.println("#0.1x"); w.println("#0.2x"); w.println("#0.3x"); w.println("#0.4x"); w.println("#0.5x"); w.println("#0.6x"); w.println("#0.7x"); w.println("#0.8x"); w.println("#0.9x"); w.println("#1.0x"); w.println(); w.println(); w.println("# Example: Sine Rate"); w.println("#"); w.println("# This pattern starts with a rate of zero operations per " + "second and increases"); w.println("# to # 100% of the target rate in a pattern that is gradual " + "at first, rapid in"); w.println("# the middle, and then gradual again at the end, and then " + "decreases back to"); w.println("# zero in a mirror image of the ascent. A graph of the " + "load generated by this"); w.println("# pattern resembles a sine wave, but starting at the " + "lowest point in the trough"); w.println("# of the wave (mathematically, represented by the function " + "'y=sin(x-pi/2)+1')."); w.println("#"); w.println("#0.000x"); w.println("#0.001x"); w.println("#0.002x"); w.println("#0.004x"); w.println("#0.006x"); w.println("#0.009x"); w.println("#0.012x"); w.println("#0.016x"); w.println("#0.020x"); w.println("#0.024x"); w.println("#0.030x"); w.println("#0.035x"); w.println("#0.041x"); w.println("#0.048x"); w.println("#0.054x"); w.println("#0.062x"); w.println("#0.070x"); w.println("#0.078x"); w.println("#0.086x"); w.println("#0.095x"); w.println("#0.105x"); w.println("#0.115x"); w.println("#0.125x"); w.println("#0.136x"); w.println("#0.146x"); w.println("#0.158x"); w.println("#0.169x"); w.println("#0.181x"); w.println("#0.194x"); w.println("#0.206x"); w.println("#0.219x"); w.println("#0.232x"); w.println("#0.245x"); w.println("#0.259x"); w.println("#0.273x"); w.println("#0.287x"); w.println("#0.301x"); w.println("#0.316x"); w.println("#0.331x"); w.println("#0.345x"); w.println("#0.361x"); w.println("#0.376x"); w.println("#0.391x"); w.println("#0.406x"); w.println("#0.422x"); w.println("#0.437x"); w.println("#0.453x"); w.println("#0.469x"); w.println("#0.484x"); w.println("#0.500x"); w.println("#0.500x"); w.println("#0.516x"); w.println("#0.531x"); w.println("#0.547x"); w.println("#0.563x"); w.println("#0.578x"); w.println("#0.594x"); w.println("#0.609x"); w.println("#0.624x"); w.println("#0.639x"); w.println("#0.655x"); w.println("#0.669x"); w.println("#0.684x"); w.println("#0.699x"); w.println("#0.713x"); w.println("#0.727x"); w.println("#0.741x"); w.println("#0.755x"); w.println("#0.768x"); w.println("#0.781x"); w.println("#0.794x"); w.println("#0.806x"); w.println("#0.819x"); w.println("#0.831x"); w.println("#0.842x"); w.println("#0.854x"); w.println("#0.864x"); w.println("#0.875x"); w.println("#0.885x"); w.println("#0.895x"); w.println("#0.905x"); w.println("#0.914x"); w.println("#0.922x"); w.println("#0.930x"); w.println("#0.938x"); w.println("#0.946x"); w.println("#0.952x"); w.println("#0.959x"); w.println("#0.965x"); w.println("#0.970x"); w.println("#0.976x"); w.println("#0.980x"); w.println("#0.984x"); w.println("#0.988x"); w.println("#0.991x"); w.println("#0.994x"); w.println("#0.996x"); w.println("#0.998x"); w.println("#0.999x"); w.println("#1.000x"); w.println("#1.000x"); w.println("#1.000x"); w.println("#0.999x"); w.println("#0.998x"); w.println("#0.996x"); w.println("#0.994x"); w.println("#0.991x"); w.println("#0.988x"); w.println("#0.984x"); w.println("#0.980x"); w.println("#0.976x"); w.println("#0.970x"); w.println("#0.965x"); w.println("#0.959x"); w.println("#0.952x"); w.println("#0.946x"); w.println("#0.938x"); w.println("#0.930x"); w.println("#0.922x"); w.println("#0.914x"); w.println("#0.905x"); w.println("#0.895x"); w.println("#0.885x"); w.println("#0.875x"); w.println("#0.864x"); w.println("#0.854x"); w.println("#0.842x"); w.println("#0.831x"); w.println("#0.819x"); w.println("#0.806x"); w.println("#0.794x"); w.println("#0.781x"); w.println("#0.768x"); w.println("#0.755x"); w.println("#0.741x"); w.println("#0.727x"); w.println("#0.713x"); w.println("#0.699x"); w.println("#0.684x"); w.println("#0.669x"); w.println("#0.655x"); w.println("#0.639x"); w.println("#0.624x"); w.println("#0.609x"); w.println("#0.594x"); w.println("#0.578x"); w.println("#0.563x"); w.println("#0.547x"); w.println("#0.531x"); w.println("#0.516x"); w.println("#0.500x"); w.println("#0.484x"); w.println("#0.469x"); w.println("#0.453x"); w.println("#0.437x"); w.println("#0.422x"); w.println("#0.406x"); w.println("#0.391x"); w.println("#0.376x"); w.println("#0.361x"); w.println("#0.345x"); w.println("#0.331x"); w.println("#0.316x"); w.println("#0.301x"); w.println("#0.287x"); w.println("#0.273x"); w.println("#0.259x"); w.println("#0.245x"); w.println("#0.232x"); w.println("#0.219x"); w.println("#0.206x"); w.println("#0.194x"); w.println("#0.181x"); w.println("#0.169x"); w.println("#0.158x"); w.println("#0.146x"); w.println("#0.136x"); w.println("#0.125x"); w.println("#0.115x"); w.println("#0.105x"); w.println("#0.095x"); w.println("#0.086x"); w.println("#0.078x"); w.println("#0.070x"); w.println("#0.062x"); w.println("#0.054x"); w.println("#0.048x"); w.println("#0.041x"); w.println("#0.035x"); w.println("#0.030x"); w.println("#0.024x"); w.println("#0.020x"); w.println("#0.016x"); w.println("#0.012x"); w.println("#0.009x"); w.println("#0.006x"); w.println("#0.004x"); w.println("#0.002x"); w.println("#0.001x"); w.println("#0.000x"); w.println(); w.println(); w.println("# Example: Sawtooth Rate"); w.println("#"); w.println("# This pattern starts with a rate of zero operations per " + "second and increases"); w.println("# linearly to 100% of the target rate. A graph of the load " + "generated by a"); w.println("# single iteration of this pattern resembles the hypotenuse " + "of a right"); w.println("# triangle, and a graph of multiple iterations resembles " + "the teeth of a saw"); w.println("# blade."); w.println("#"); w.println("#0.00x"); w.println("#0.01x"); w.println("#0.02x"); w.println("#0.03x"); w.println("#0.04x"); w.println("#0.05x"); w.println("#0.06x"); w.println("#0.07x"); w.println("#0.08x"); w.println("#0.09x"); w.println("#0.10x"); w.println("#0.11x"); w.println("#0.12x"); w.println("#0.13x"); w.println("#0.14x"); w.println("#0.15x"); w.println("#0.16x"); w.println("#0.17x"); w.println("#0.18x"); w.println("#0.19x"); w.println("#0.20x"); w.println("#0.21x"); w.println("#0.22x"); w.println("#0.23x"); w.println("#0.24x"); w.println("#0.25x"); w.println("#0.26x"); w.println("#0.27x"); w.println("#0.28x"); w.println("#0.29x"); w.println("#0.30x"); w.println("#0.31x"); w.println("#0.32x"); w.println("#0.33x"); w.println("#0.34x"); w.println("#0.35x"); w.println("#0.36x"); w.println("#0.37x"); w.println("#0.38x"); w.println("#0.39x"); w.println("#0.40x"); w.println("#0.41x"); w.println("#0.42x"); w.println("#0.43x"); w.println("#0.44x"); w.println("#0.45x"); w.println("#0.46x"); w.println("#0.47x"); w.println("#0.48x"); w.println("#0.49x"); w.println("#0.50x"); w.println("#0.51x"); w.println("#0.52x"); w.println("#0.53x"); w.println("#0.54x"); w.println("#0.55x"); w.println("#0.56x"); w.println("#0.57x"); w.println("#0.58x"); w.println("#0.59x"); w.println("#0.60x"); w.println("#0.61x"); w.println("#0.62x"); w.println("#0.63x"); w.println("#0.64x"); w.println("#0.65x"); w.println("#0.66x"); w.println("#0.67x"); w.println("#0.68x"); w.println("#0.69x"); w.println("#0.70x"); w.println("#0.71x"); w.println("#0.72x"); w.println("#0.73x"); w.println("#0.74x"); w.println("#0.75x"); w.println("#0.76x"); w.println("#0.77x"); w.println("#0.78x"); w.println("#0.79x"); w.println("#0.80x"); w.println("#0.81x"); w.println("#0.82x"); w.println("#0.83x"); w.println("#0.84x"); w.println("#0.85x"); w.println("#0.86x"); w.println("#0.87x"); w.println("#0.88x"); w.println("#0.89x"); w.println("#0.90x"); w.println("#0.91x"); w.println("#0.92x"); w.println("#0.93x"); w.println("#0.94x"); w.println("#0.95x"); w.println("#0.96x"); w.println("#0.97x"); w.println("#0.98x"); w.println("#0.99x"); w.println("#1.00x"); w.println(); w.println(); w.println("# Example: Triangle Rate"); w.println("#"); w.println("# This pattern starts with a rate of zero operations per " + "second and increases"); w.println("# linearly to 100% of the target rate before decreasing " + "linearly back to 0%."); w.println("# A graph of the load generated by a single iteration of " + "this tool is like that"); w.println("# of the sawtooth pattern above followed immediately by its " + "mirror image."); w.println("#"); w.println("#0.00x"); w.println("#0.01x"); w.println("#0.02x"); w.println("#0.03x"); w.println("#0.04x"); w.println("#0.05x"); w.println("#0.06x"); w.println("#0.07x"); w.println("#0.08x"); w.println("#0.09x"); w.println("#0.10x"); w.println("#0.11x"); w.println("#0.12x"); w.println("#0.13x"); w.println("#0.14x"); w.println("#0.15x"); w.println("#0.16x"); w.println("#0.17x"); w.println("#0.18x"); w.println("#0.19x"); w.println("#0.20x"); w.println("#0.21x"); w.println("#0.22x"); w.println("#0.23x"); w.println("#0.24x"); w.println("#0.25x"); w.println("#0.26x"); w.println("#0.27x"); w.println("#0.28x"); w.println("#0.29x"); w.println("#0.30x"); w.println("#0.31x"); w.println("#0.32x"); w.println("#0.33x"); w.println("#0.34x"); w.println("#0.35x"); w.println("#0.36x"); w.println("#0.37x"); w.println("#0.38x"); w.println("#0.39x"); w.println("#0.40x"); w.println("#0.41x"); w.println("#0.42x"); w.println("#0.43x"); w.println("#0.44x"); w.println("#0.45x"); w.println("#0.46x"); w.println("#0.47x"); w.println("#0.48x"); w.println("#0.49x"); w.println("#0.50x"); w.println("#0.51x"); w.println("#0.52x"); w.println("#0.53x"); w.println("#0.54x"); w.println("#0.55x"); w.println("#0.56x"); w.println("#0.57x"); w.println("#0.58x"); w.println("#0.59x"); w.println("#0.60x"); w.println("#0.61x"); w.println("#0.62x"); w.println("#0.63x"); w.println("#0.64x"); w.println("#0.65x"); w.println("#0.66x"); w.println("#0.67x"); w.println("#0.68x"); w.println("#0.69x"); w.println("#0.70x"); w.println("#0.71x"); w.println("#0.72x"); w.println("#0.73x"); w.println("#0.74x"); w.println("#0.75x"); w.println("#0.76x"); w.println("#0.77x"); w.println("#0.78x"); w.println("#0.79x"); w.println("#0.80x"); w.println("#0.81x"); w.println("#0.82x"); w.println("#0.83x"); w.println("#0.84x"); w.println("#0.85x"); w.println("#0.86x"); w.println("#0.87x"); w.println("#0.88x"); w.println("#0.89x"); w.println("#0.90x"); w.println("#0.91x"); w.println("#0.92x"); w.println("#0.93x"); w.println("#0.94x"); w.println("#0.95x"); w.println("#0.96x"); w.println("#0.97x"); w.println("#0.98x"); w.println("#0.99x"); w.println("#1.00x"); w.println("#0.99x"); w.println("#0.98x"); w.println("#0.97x"); w.println("#0.96x"); w.println("#0.95x"); w.println("#0.94x"); w.println("#0.93x"); w.println("#0.92x"); w.println("#0.91x"); w.println("#0.90x"); w.println("#0.89x"); w.println("#0.88x"); w.println("#0.87x"); w.println("#0.86x"); w.println("#0.85x"); w.println("#0.84x"); w.println("#0.83x"); w.println("#0.82x"); w.println("#0.81x"); w.println("#0.80x"); w.println("#0.79x"); w.println("#0.78x"); w.println("#0.77x"); w.println("#0.76x"); w.println("#0.75x"); w.println("#0.74x"); w.println("#0.73x"); w.println("#0.72x"); w.println("#0.71x"); w.println("#0.70x"); w.println("#0.69x"); w.println("#0.68x"); w.println("#0.67x"); w.println("#0.66x"); w.println("#0.65x"); w.println("#0.64x"); w.println("#0.63x"); w.println("#0.62x"); w.println("#0.61x"); w.println("#0.60x"); w.println("#0.59x"); w.println("#0.58x"); w.println("#0.57x"); w.println("#0.56x"); w.println("#0.55x"); w.println("#0.54x"); w.println("#0.53x"); w.println("#0.52x"); w.println("#0.51x"); w.println("#0.50x"); w.println("#0.49x"); w.println("#0.48x"); w.println("#0.47x"); w.println("#0.46x"); w.println("#0.45x"); w.println("#0.44x"); w.println("#0.43x"); w.println("#0.42x"); w.println("#0.41x"); w.println("#0.40x"); w.println("#0.39x"); w.println("#0.38x"); w.println("#0.37x"); w.println("#0.36x"); w.println("#0.35x"); w.println("#0.34x"); w.println("#0.33x"); w.println("#0.32x"); w.println("#0.31x"); w.println("#0.30x"); w.println("#0.29x"); w.println("#0.28x"); w.println("#0.27x"); w.println("#0.26x"); w.println("#0.25x"); w.println("#0.24x"); w.println("#0.23x"); w.println("#0.22x"); w.println("#0.21x"); w.println("#0.20x"); w.println("#0.19x"); w.println("#0.18x"); w.println("#0.17x"); w.println("#0.16x"); w.println("#0.15x"); w.println("#0.14x"); w.println("#0.13x"); w.println("#0.12x"); w.println("#0.11x"); w.println("#0.10x"); w.println("#0.09x"); w.println("#0.08x"); w.println("#0.07x"); w.println("#0.06x"); w.println("#0.05x"); w.println("#0.04x"); w.println("#0.03x"); w.println("#0.02x"); w.println("#0.01x"); w.println("#0.00x"); w.println(); w.println(); w.println("# Example: 'Hockey Stick' Rate"); w.println("#"); w.println("# This pattern starts with a rate of zero operations per " + "second and increases"); w.println("# slowly at first before ramping up much more quickly. A " + "graph of the load"); w.println("# generated by a single iteration of this pattern vaguely " + "resembles a hockey"); w.println("# stick."); w.println("#"); w.println("#0.000x"); w.println("#0.000x"); w.println("#0.000x"); w.println("#0.000x"); w.println("#0.000x"); w.println("#0.000x"); w.println("#0.000x"); w.println("#0.000x"); w.println("#0.001x"); w.println("#0.001x"); w.println("#0.001x"); w.println("#0.001x"); w.println("#0.002x"); w.println("#0.002x"); w.println("#0.003x"); w.println("#0.003x"); w.println("#0.004x"); w.println("#0.005x"); w.println("#0.006x"); w.println("#0.007x"); w.println("#0.008x"); w.println("#0.009x"); w.println("#0.011x"); w.println("#0.012x"); w.println("#0.014x"); w.println("#0.016x"); w.println("#0.018x"); w.println("#0.020x"); w.println("#0.022x"); w.println("#0.024x"); w.println("#0.027x"); w.println("#0.030x"); w.println("#0.033x"); w.println("#0.036x"); w.println("#0.039x"); w.println("#0.043x"); w.println("#0.047x"); w.println("#0.051x"); w.println("#0.055x"); w.println("#0.059x"); w.println("#0.064x"); w.println("#0.069x"); w.println("#0.074x"); w.println("#0.080x"); w.println("#0.085x"); w.println("#0.091x"); w.println("#0.097x"); w.println("#0.104x"); w.println("#0.111x"); w.println("#0.118x"); w.println("#0.125x"); w.println("#0.133x"); w.println("#0.141x"); w.println("#0.149x"); w.println("#0.157x"); w.println("#0.166x"); w.println("#0.176x"); w.println("#0.185x"); w.println("#0.195x"); w.println("#0.205x"); w.println("#0.216x"); w.println("#0.227x"); w.println("#0.238x"); w.println("#0.250x"); w.println("#0.262x"); w.println("#0.275x"); w.println("#0.287x"); w.println("#0.301x"); w.println("#0.314x"); w.println("#0.329x"); w.println("#0.343x"); w.println("#0.358x"); w.println("#0.373x"); w.println("#0.389x"); w.println("#0.405x"); w.println("#0.422x"); w.println("#0.439x"); w.println("#0.457x"); w.println("#0.475x"); w.println("#0.493x"); w.println("#0.512x"); w.println("#0.531x"); w.println("#0.551x"); w.println("#0.572x"); w.println("#0.593x"); w.println("#0.614x"); w.println("#0.636x"); w.println("#0.659x"); w.println("#0.681x"); w.println("#0.705x"); w.println("#0.729x"); w.println("#0.754x"); w.println("#0.779x"); w.println("#0.804x"); w.println("#0.831x"); w.println("#0.857x"); w.println("#0.885x"); w.println("#0.913x"); w.println("#0.941x"); w.println("#0.970x"); w.println("#1.000x"); w.println(); } finally { w.close(); } } /** * Constructs a new RateAdjustor with the specified parameters. See the * class-level javadoc for more information. * * @param barrier The barrier to update based on the specified * rates. * @param baseRatePerSecond The baseline rate per second, or 0 if none was * specified. * @param rates A list of rates and durations as described in * the class-level javadoc. The reader will * always be closed before this method returns. * * @throws IOException If there is a problem reading from * the rates Reader. * @throws IllegalArgumentException If there is a problem with the rates * input. */ public RateAdjustor(@NotNull final FixedRateBarrier barrier, final long baseRatePerSecond, @NotNull final Reader rates) throws IOException, IllegalArgumentException { // Read the header first. final List lines; try { Validator.ensureNotNull(barrier, rates); setDaemon(true); this.barrier = barrier; lines = readLines(rates); } finally { rates.close(); } final Map header = consumeHeader(lines); final Set invalidKeys = new LinkedHashSet<>(header.keySet()); invalidKeys.removeAll(KEYS); if (! invalidKeys.isEmpty()) { throw new IllegalArgumentException( ERR_RATE_ADJUSTOR_INVALID_KEYS.get(invalidKeys, KEYS)); } final String format = header.get(FORMAT_KEY); if (format == null) { throw new IllegalArgumentException(ERR_RATE_ADJUSTOR_MISSING_FORMAT.get( FORMAT_KEY, FORMATS, COMMENT_START)); } if (! format.equals(FORMAT_VALUE_RATE_DURATION)) { // For now this is the only format that we support. throw new IllegalArgumentException( ERR_RATE_ADJUSTOR_INVALID_FORMAT.get(format, FORMAT_KEY, FORMATS)); } repeat = Boolean.parseBoolean(header.get(REPEAT_KEY)); // This will be non-zero if it's set in the input. long defaultDurationMillis = 0; final String defaultDurationStr = header.get(DEFAULT_DURATION_KEY); if (defaultDurationStr != null) { try { defaultDurationMillis = DurationArgument.parseDuration( defaultDurationStr, TimeUnit.MILLISECONDS); } catch (final ArgumentException e) { Debug.debugException(e); throw new IllegalArgumentException( ERR_RATE_ADJUSTOR_INVALID_DEFAULT_DURATION.get( defaultDurationStr, e.getExceptionMessage()), e); } } // Now parse out the rates and durations, which will look like this: // 1000,1s // 1.5,1d // 0.5X, 1m // # Duration can be omitted if default-duration header was included. // 1000 final List> ratesAndDurationList = new ArrayList<>(10); final Pattern splitPattern = Pattern.compile("\\s*,\\s*"); for (final String fullLine: lines) { // Strip out comments and white space. String line = fullLine; final int commentStart = fullLine.indexOf(COMMENT_START); if (commentStart >= 0) { line = line.substring(0, commentStart); } line = line.trim(); if (line.isEmpty()) { continue; } final String[] fields = splitPattern.split(line); if (!((fields.length == 2) || ((fields.length == 1) && defaultDurationMillis != 0))) { throw new IllegalArgumentException(ERR_RATE_ADJUSTOR_INVALID_LINE.get( fullLine, DEFAULT_DURATION_KEY)); } String rateStr = fields[0]; boolean isRateMultiplier = false; if (rateStr.endsWith("X") || rateStr.endsWith("x")) { rateStr = rateStr.substring(0, rateStr.length() - 1).trim(); isRateMultiplier = true; } double rate; try { rate = Double.parseDouble(rateStr); } catch (final NumberFormatException e) { Debug.debugException(e); throw new IllegalArgumentException( ERR_RATE_ADJUSTOR_INVALID_RATE.get(rateStr, fullLine), e); } // Values that look like 2X are a multiplier on the base rate. if (isRateMultiplier) { if (baseRatePerSecond <= 0) { throw new IllegalArgumentException( ERR_RATE_ADJUSTOR_RELATIVE_RATE_WITHOUT_BASELINE.get( rateStr, fullLine)); } rate *= baseRatePerSecond; } final long durationMillis; if (fields.length < 2) { durationMillis = defaultDurationMillis; } else { final String duration = fields[1]; try { durationMillis = DurationArgument.parseDuration( duration, TimeUnit.MILLISECONDS); } catch (final ArgumentException e) { Debug.debugException(e); throw new IllegalArgumentException( ERR_RATE_ADJUSTOR_INVALID_DURATION.get(duration, fullLine, e.getExceptionMessage()), e); } } ratesAndDurationList.add(new ObjectPair<>(rate, durationMillis)); } ratesAndDurations = Collections.unmodifiableList(ratesAndDurationList); } /** * Starts this thread and waits for the initial rate to be set. */ @Override public void start() { super.start(); // Wait until the initial rate is set. Assuming the caller starts this // RateAdjustor before the FixedRateBarrier is used by other threads, // this will guarantee that the initial rate is in place before the // barrier is used. try { initialRateSetLatch.await(); } catch (final InterruptedException e) { Debug.debugException(e); Thread.currentThread().interrupt(); } } /** * Adjusts the rate in FixedRateBarrier as described in the rates. */ @Override public void run() { try { if (ratesAndDurations.isEmpty()) { return; } do { final List> ratesAndEndTimes = new ArrayList<>(ratesAndDurations.size()); long endTime = System.currentTimeMillis(); for (final ObjectPair rateAndDuration : ratesAndDurations) { endTime += rateAndDuration.getSecond(); ratesAndEndTimes.add(new ObjectPair<>(rateAndDuration.getFirst(), endTime)); } for (final ObjectPair rateAndEndTime: ratesAndEndTimes) { if (shutDown) { return; } final double rate = rateAndEndTime.getFirst(); final long intervalMillis = barrier.getTargetRate().getFirst(); final int perInterval = calculatePerInterval(intervalMillis, rate); barrier.setRate(intervalMillis, perInterval); // Signal start() that we've set the initial rate. if (initialRateSetLatch.getCount() > 0) { initialRateSetLatch.countDown(); } // Hold at this rate for the specified duration. final long durationMillis = rateAndEndTime.getSecond() - System.currentTimeMillis(); if (durationMillis > 0L) { sleeper.sleep(durationMillis); } } } while (repeat); } finally { // Just in case we happened to be shutdown before we were started. // We still want start() to be able to return. if (initialRateSetLatch.getCount() > 0) { initialRateSetLatch.countDown(); } } } /** * Signals this to shut down. */ public void shutDown() { shutDown = true; sleeper.wakeup(); } /** * Returns the of rates and durations. This is primarily here for testing * purposes. * * @return The list of rates and durations. */ @NotNull() List> getRatesAndDurations() { return ratesAndDurations; } /** * Calculates the rate per interval given the specified interval width * and the target rate per second. (This is static and non-private so that * it can be unit tested.) * * @param intervalDurationMillis The duration of the interval in * milliseconds. * @param ratePerSecond The target rate per second. * * @return The rate per interval, which will be at least 1. */ static int calculatePerInterval(final long intervalDurationMillis, final double ratePerSecond) { final double intervalDurationSeconds = intervalDurationMillis / 1000.0; final double ratePerInterval = ratePerSecond * intervalDurationSeconds; return (int)Math.max(1, Math.round(ratePerInterval)); } /** * This reads the header at the start of the file. All blank lines and * comment lines will be ignored. The end of the header will be signified by * a line containing only the text "END HEADER". All non-blank, non-comment * lines in the header must be in the format "name=value", where there may be * zero or more spaces on either side of the equal sign, the name must not * contain either the space or the equal sign character, and the value must * not begin or end with a space. Header lines must not contain partial-line * comments. * * @param lines The lines of input that include the header. * * @return A map of key/value pairs extracted from the header. * * @throws IllegalArgumentException If a problem is encountered while * parsing the header (e.g., a malformed * header line is encountered, multiple * headers have the same key, there is no * end of header marker, etc.). */ @NotNull() static Map consumeHeader(@NotNull final List lines) throws IllegalArgumentException { // The header will look like this: // key1=value1 // key2 = value2 // END HEADER boolean endHeaderFound = false; final Map headerMap = new LinkedHashMap<>(StaticUtils.computeMapCapacity(3)); final Iterator lineIter = lines.iterator(); while (lineIter.hasNext()) { final String line = lineIter.next().trim(); lineIter.remove(); if (line.isEmpty() || line.startsWith(String.valueOf(COMMENT_START))) { continue; } if (line.equalsIgnoreCase(END_HEADER_TEXT)) { endHeaderFound = true; break; } final int equalPos = line.indexOf('='); if (equalPos < 0) { throw new IllegalArgumentException( ERR_RATE_ADJUSTOR_HEADER_NO_EQUAL.get(line)); } final String key = line.substring(0, equalPos).trim(); if (key.isEmpty()) { throw new IllegalArgumentException( ERR_RATE_ADJUSTOR_HEADER_EMPTY_KEY.get(line)); } final String newValue = line.substring(equalPos+1).trim(); final String existingValue = headerMap.get(key); if (existingValue != null) { throw new IllegalArgumentException( ERR_RATE_ADJUSTOR_DUPLICATE_HEADER_KEY.get(key, existingValue, newValue)); } headerMap.put(key, newValue); } if (! endHeaderFound) { // This means we iterated across all lines without finding the end header // marker. throw new IllegalArgumentException( ERR_RATE_ADJUSTOR_NO_END_HEADER_FOUND.get(END_HEADER_TEXT)); } return headerMap; } /** * Returns a list of the lines read from the specified Reader. * * @param reader The Reader to read from. * * @return A list of the lines read from the specified Reader. * * @throws IOException If there is a problem reading from the Reader. */ @NotNull() private static List readLines(@NotNull final Reader reader) throws IOException { final BufferedReader bufferedReader = new BufferedReader(reader); // We remove items from the front of the list, so a linked list works best. final List lines = new LinkedList<>(); String line; while ((line = bufferedReader.readLine()) != null) { lines.add(line); } return lines; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy