001/* 002 * Copyright 2014-2024 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2014-2024 Ping Identity Corporation 007 * 008 * Licensed under the Apache License, Version 2.0 (the "License"); 009 * you may not use this file except in compliance with the License. 010 * You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, software 015 * distributed under the License is distributed on an "AS IS" BASIS, 016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 017 * See the License for the specific language governing permissions and 018 * limitations under the License. 019 */ 020/* 021 * Copyright (C) 2014-2024 Ping Identity Corporation 022 * 023 * This program is free software; you can redistribute it and/or modify 024 * it under the terms of the GNU General Public License (GPLv2 only) 025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 026 * as published by the Free Software Foundation. 027 * 028 * This program is distributed in the hope that it will be useful, 029 * but WITHOUT ANY WARRANTY; without even the implied warranty of 030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 031 * GNU General Public License for more details. 032 * 033 * You should have received a copy of the GNU General Public License 034 * along with this program; if not, see <http://www.gnu.org/licenses>. 035 */ 036package com.unboundid.util; 037 038 039 040import java.io.BufferedReader; 041import java.io.File; 042import java.io.FileReader; 043import java.io.IOException; 044import java.io.PrintWriter; 045import java.io.Reader; 046import java.util.ArrayList; 047import java.util.Arrays; 048import java.util.Collections; 049import java.util.Iterator; 050import java.util.LinkedHashMap; 051import java.util.LinkedHashSet; 052import java.util.LinkedList; 053import java.util.List; 054import java.util.Map; 055import java.util.Set; 056import java.util.concurrent.CountDownLatch; 057import java.util.concurrent.TimeUnit; 058import java.util.regex.Pattern; 059 060import com.unboundid.util.args.ArgumentException; 061import com.unboundid.util.args.DurationArgument; 062 063import static com.unboundid.util.UtilityMessages.*; 064 065 066 067/** 068 * This class allows a FixedRateBarrier to change dynamically. The rate changes 069 * are governed by lines read from a {@code Reader} (typically backed by a 070 * file). The input starts with a header that provides some global options and 071 * then has a list of lines, where each line contains a single rate per second, 072 * a comma, and a duration to maintain that rate. Rates are specified as an 073 * absolute rate per second or as a rate relative to the base rate per second. 074 * The duration is an integer followed by a time unit (ms=milliseconds, 075 * s=seconds, m=minutes, h=hours, and d=days). 076 * <BR><BR> 077 * The following simple example will run at a target rate of 1000 per second 078 * for one minute, and then 10000 per second for 10 seconds. 079 * <pre> 080 * # format=rate-duration 081 * 1000,1m 082 * 10000,10s 083 * </pre> 084 * <BR> 085 * The following example has a default duration of one minute, and will repeat 086 * the two intervals until this RateAdjustor is shut down. The first interval 087 * is run for the default of 1 minute at two and half times the base rate, and 088 * then run for 10 seconds at 10000 per second. 089 * <pre> 090 * # format=rate-duration 091 * # default-duration=1m 092 * # repeat=true 093 * 2.5X 094 * 10000,10s 095 * </pre> 096 * A {@code RateAdjustor} is a daemon thread. It is necessary to call the 097 * {@code start()} method to start the thread and begin the rate changes. 098 * Once this finished processing the rates, the thread will complete. 099 * It can be stopped prematurely by calling {@code shutDown()}. 100 * <BR><BR> 101 * The header can contain the following options: 102 * <UL> 103 * <LI>{@code format} (required): This must currently have the value 104 * {@code rate-duration}.</LI> 105 * <LI>{@code default-duration} (optional): This can specify a default 106 * duration for intervals that do not include a duration. The format 107 * is an integer followed by a time unit as described above.</LI> 108 * <LI>{@code repeat} (optional): If this has a value of {@code true}, then 109 * the rates in the input will be repeated until {@code shutDown()} is 110 * called.</LI> 111 * </UL> 112 */ 113@ThreadSafety(level = ThreadSafetyLevel.MOSTLY_THREADSAFE) 114public final class RateAdjustor extends Thread 115{ 116 /** 117 * This starts a comment in the input. 118 */ 119 public static final char COMMENT_START = '#'; 120 121 122 123 /** 124 * The text that must appear on a line by itself in order to denote that the 125 * end of the file header has been reached. 126 */ 127 @NotNull public static final String END_HEADER_TEXT = "END HEADER"; 128 129 130 131 /** 132 * The header key that represents the default duration. 133 */ 134 @NotNull public static final String DEFAULT_DURATION_KEY = "default-duration"; 135 136 137 138 /** 139 * The header key that represents the format of the file. 140 */ 141 @NotNull public static final String FORMAT_KEY = "format"; 142 143 144 145 /** 146 * The value of the format key that represents a list of rates and durations 147 * within the input file. 148 */ 149 @NotNull public static final String FORMAT_VALUE_RATE_DURATION = 150 "rate-and-duration"; 151 152 153 154 /** 155 * A list of all formats that we support. 156 */ 157 @NotNull public static final List<String> FORMATS = 158 Collections.singletonList(FORMAT_VALUE_RATE_DURATION); 159 160 161 162 /** 163 * The header key that represents whether the input should be repeated. 164 */ 165 @NotNull public static final String REPEAT_KEY = "repeat"; 166 167 168 169 /** 170 * A list of all header keys that we support. 171 */ 172 @NotNull public static final List<String> KEYS = 173 Arrays.asList(DEFAULT_DURATION_KEY, FORMAT_KEY, REPEAT_KEY); 174 175 176 177 // Other headers to consider: 178 // * rate-multiplier, so you can easily proportionally increase or decrease 179 // every target rate without changing all the target rates directly. 180 // * duration-multiplier, so you can easily proportionally increase or 181 // decrease the length of time to spend at target rates. 182 // * rate-change-behavior, so you can specify the behavior that should be 183 // exhibited when transitioning from one rate to another (e.g., instant 184 // jump, linear acceleration, sine-based acceleration, etc.). 185 // * jitter, so we can introduce some amount of random jitter in the target 186 // rate (in which the actual target rate may be frequently adjusted to be 187 // slightly higher or lower than the designated target rate). 188 // * spike, so we can introduce periodic, substantial increases in the target 189 // rate. 190 191 192 193 // The barrier whose rate is adjusted. 194 @NotNull private final FixedRateBarrier barrier; 195 196 // A list of rates per second and the number of milliseconds that the 197 // specified rate should be maintained. 198 @NotNull private final List<ObjectPair<Double,Long>> ratesAndDurations; 199 200 // If this is true, then the ratesAndDurations will be repeated until this is 201 // shut down. 202 private final boolean repeat; 203 204 // Set to true when this should shut down. 205 private volatile boolean shutDown = false; 206 207 // This is used to make sure we set the initial rate before start() returns. 208 @NotNull private final CountDownLatch initialRateSetLatch = 209 new CountDownLatch(1); 210 211 // This allows us to interrupt when we are sleeping. 212 @NotNull private final WakeableSleeper sleeper = new WakeableSleeper(); 213 214 215 216 /** 217 * Returns a new RateAdjustor with the specified parameters. See the 218 * class-level javadoc for more information. 219 * 220 * @param barrier The barrier to update based on the specified 221 * rates. 222 * @param baseRatePerSecond The baseline rate per second, or {@code null} 223 * if none was specified. 224 * @param rates A file containing a list of rates and durations 225 * as described in the class-level javadoc. 226 * 227 * @return A new RateAdjustor constructed from the specified parameters. 228 * 229 * @throws IOException If there is a problem reading from 230 * the rates Reader. 231 * @throws IllegalArgumentException If there is a problem with the rates 232 * input. 233 */ 234 @NotNull() 235 public static RateAdjustor newInstance( 236 @NotNull final FixedRateBarrier barrier, 237 @Nullable final Integer baseRatePerSecond, 238 @NotNull final File rates) 239 throws IOException, IllegalArgumentException 240 { 241 final Reader reader = new FileReader(rates); 242 return new RateAdjustor( 243 barrier, 244 (baseRatePerSecond == null) ? 0 : baseRatePerSecond, 245 reader); 246 } 247 248 249 250 /** 251 * Retrieves a string that may be used as the description of the argument that 252 * specifies the path to a variable rate data file for use in conjunction with 253 * this rate adjustor. 254 * 255 * @param genArgName The name of the argument that may be used to generate a 256 * sample variable rate data file. 257 * 258 * @return A string that may be used as the description of the argument that 259 * specifies the path to a variable rate data file for use in 260 * conjunction with this rate adjustor. 261 */ 262 @Nullable() 263 public static String getVariableRateDataArgumentDescription( 264 @NotNull final String genArgName) 265 { 266 return INFO_RATE_ADJUSTOR_VARIABLE_RATE_DATA_ARG_DESCRIPTION.get( 267 genArgName); 268 } 269 270 271 272 /** 273 * Retrieves a string that may be used as the description of the argument that 274 * generates a sample variable rate data file that serves as documentation of 275 * the variable rate data format. 276 * 277 * @param dataFileArgName The name of the argument that specifies the path 278 * to a file 279 * 280 * @return A string that may be used as the description of the argument that 281 * generates a sample variable rate data file that serves as 282 * documentation of the variable rate data format. 283 */ 284 @Nullable() 285 public static String getGenerateSampleVariableRateFileDescription( 286 @NotNull final String dataFileArgName) 287 { 288 return INFO_RATE_ADJUSTOR_GENERATE_SAMPLE_RATE_FILE_ARG_DESCRIPTION.get( 289 dataFileArgName); 290 } 291 292 293 294 /** 295 * Writes a sample variable write data file to the specified location. 296 * 297 * @param f The path to the file to be written. 298 * 299 * @throws IOException If a problem is encountered while writing to the 300 * specified file. 301 */ 302 public static void writeSampleVariableRateFile(@NotNull final File f) 303 throws IOException 304 { 305 final PrintWriter w = new PrintWriter(f); 306 try 307 { 308 w.println("# This is an example variable rate data file. All blank " + 309 "lines will be ignored."); 310 w.println("# All lines starting with the '#' character are considered " + 311 "comments and will"); 312 w.println("# also be ignored."); 313 w.println(); 314 w.println("# The beginning of the file must be a header containing " + 315 "properties pertaining"); 316 w.println("# to the variable rate data. All headers must be in the " + 317 "format 'name=value',"); 318 w.println("# in which any spaces surrounding the equal sign will be " + 319 "ignored."); 320 w.println(); 321 w.println("# The first header should be the 'format' header, which " + 322 "specifies the format"); 323 w.println("# for the variable rate data file. This header is " + 324 "required. At present, the"); 325 w.println("# only supported format is 'rate-and-duration', although " + 326 "additional formats may"); 327 w.println("# be added in the future."); 328 w.println("format = rate-and-duration"); 329 w.println(); 330 w.println("# The optional 'default-duration' header may be used to " + 331 "specify a duration that"); 332 w.println("# will be used for any interval that does not explicitly " + 333 "specify a duration."); 334 w.println("# The duration must consist of a positive integer value " + 335 "followed by a time"); 336 w.println("# unit (with zero or more spaces separating the integer " + 337 "value from the unit)."); 338 w.println("# The supported time units are:"); 339 w.println("#"); 340 w.println("# - nanoseconds, nanosecond, nanos, nano, ns"); 341 w.println("# - microseconds, microseconds, micros, micro, us"); 342 w.println("# - milliseconds, millisecond, millis, milli, ms"); 343 w.println("# - seconds, second, secs, sec, s"); 344 w.println("# - minutes, minute, mins, min, m"); 345 w.println("# - hours, hour, hrs, hr, h"); 346 w.println("# - days, day, d"); 347 w.println("#"); 348 w.println("# If no 'default-duration' header is present, then every " + 349 "data interval must"); 350 w.println("# include an explicitly-specified duration."); 351 w.println("default-duration = 10 seconds"); 352 w.println(); 353 w.println("# The optional 'repeat' header may be used to indicate how " + 354 "the tool should"); 355 w.println("# behave once the end of the variable rate data definitions " + 356 "has been reached."); 357 w.println("# If the 'repeat' header is present with a value of 'true', " + 358 "then the tool will"); 359 w.println("# operate in an endless loop, returning to the beginning of " + 360 "the variable rate"); 361 w.println("# definitions once the end has been reached. If the " + 362 "'repeat' header is present"); 363 w.println("# with a value of 'false', or if the 'repeat' header is " + 364 "absent, then the tool"); 365 w.println("# will exit after it has processed all of the variable " + 366 "rate definitions."); 367 w.println("repeat = true"); 368 w.println(); 369 w.println("# After all header properties have been specified, the end " + 370 "of the header must"); 371 w.println("# be signified with a line containing only the text 'END " + 372 "HEADER'."); 373 w.println("END HEADER"); 374 w.println(); 375 w.println(); 376 w.println("# After the header is complete, the variable rate " + 377 "definitions should be"); 378 w.println("# provided. Each definition should be given on a line by " + 379 "itself, and should"); 380 w.println("# contain a target rate per second and an optional length " + 381 "of time to maintain"); 382 w.println("# that rate."); 383 w.println("#"); 384 w.println("# The target rate must always be present in a variable " + 385 "rate definition. It may"); 386 w.println("# be either a positive integer value that specifies the " + 387 "absolute target rate"); 388 w.println("# per second (e.g., a value of '1000' indicates a target " + 389 "rate of 1000"); 390 w.println("# operations per second), or it may be a floating-point " + 391 "value followed by the"); 392 w.println("# letter 'x' to indicate that it is a multiplier of the " + 393 "value specified by the"); 394 w.println("# '--ratePerSecond' argument (e.g., if the " + 395 "'--ratePerSecond' argument is"); 396 w.println("# present with a value of 1000, then a target rate value " + 397 "of '0.75x' indicates a"); 398 w.println("# target rate that is 75% of the '--ratePerSecond' value, " + 399 "or 750 operations per"); 400 w.println("# second). If the latter format is used, then the " + 401 "'--ratePerSecond' argument"); 402 w.println("# must be provided."); 403 w.println("#"); 404 w.println("# The duration may optionally be present in a variable " + 405 "rate definition. If"); 406 w.println("# present, it must be separated from the target rate by a " + 407 "comma (and there may"); 408 w.println("# be zero or more spaces on either side of the comma). " + 409 "The duration must be in"); 410 w.println("# the same format as specified in the description of the " + 411 "'default-duration'"); 412 w.println("# header above (i.e., a positive integer followed by a " + 413 "time unit). If a"); 414 w.println("# variable rate definition does not include a duration, " + 415 "then the"); 416 w.println("# 'default-duration' header must have been specified, and " + 417 "that default duration"); 418 w.println("# will be used for that variable rate definition."); 419 w.println("#"); 420 w.println("# The following variable rate definitions may be used to " + 421 "stairstep the target"); 422 w.println("# rate from 1000 operations per second to 10000 operations " + 423 "per second, in"); 424 w.println("# increments of 1000 operations per second, spending one " + 425 "minute at each level."); 426 w.println("# If the 'repeat' header is present with a value of 'true', " + 427 "then the process"); 428 w.println("# will start back over at 1000 operations per second after " + 429 "completing one"); 430 w.println("# minute at 10000 operations per second. Otherwise, the " + 431 "tool will exit after"); 432 w.println("# completing the 10000 operation-per-second interval."); 433 w.println("1000, 1 minute"); 434 w.println("2000, 1 minute"); 435 w.println("3000, 1 minute"); 436 w.println("4000, 1 minute"); 437 w.println("5000, 1 minute"); 438 w.println("6000, 1 minute"); 439 w.println("7000, 1 minute"); 440 w.println("8000, 1 minute"); 441 w.println("9000, 1 minute"); 442 w.println("10000, 1 minute"); 443 w.println(); 444 w.println(); 445 w.println("# Additional sample rate definitions that represent common " + 446 "load patterns are"); 447 w.println("# provided below. Each of these patterns makes use of the " + 448 "relative format for"); 449 w.println("# the target rate and therefore require the " + 450 "'--ratePerSecond' argument to"); 451 w.println("# specify the target rate. These sample rate definitions " + 452 "are commented out to"); 453 w.println("# prevent them from being interpreted by default."); 454 w.println(); 455 w.println(); 456 w.println("# Example: Square Rate"); 457 w.println("#"); 458 w.println("# This pattern starts with a rate of zero operations per " + 459 "second, then"); 460 w.println("# immediately jumps to a rate of 100% of the target rate. " + 461 "A graph of the load"); 462 w.println("# generated by repeating iterations of this pattern " + 463 "represents a series of"); 464 w.println("# squares that are alternately missing the top and bottom " + 465 "edges."); 466 w.println("#"); 467 w.println("#0.00x"); 468 w.println("#1.00x"); 469 w.println(); 470 w.println(); 471 w.println("# Example: Stairstep Rate"); 472 w.println("#"); 473 w.println("# This pattern starts with a rate that is 10% of the target " + 474 "rate, then jumps to"); 475 w.println("# 20% of the target rate, then 30%, 40%, 50%, etc. until it " + 476 "reaches 100% of the"); 477 w.println("# target rate. A graph of the load generated by a single " + 478 "iteration of this"); 479 w.println("# pattern represents a series of stair steps."); 480 w.println("#"); 481 w.println("#0.1x"); 482 w.println("#0.2x"); 483 w.println("#0.3x"); 484 w.println("#0.4x"); 485 w.println("#0.5x"); 486 w.println("#0.6x"); 487 w.println("#0.7x"); 488 w.println("#0.8x"); 489 w.println("#0.9x"); 490 w.println("#1.0x"); 491 w.println(); 492 w.println(); 493 w.println("# Example: Sine Rate"); 494 w.println("#"); 495 w.println("# This pattern starts with a rate of zero operations per " + 496 "second and increases"); 497 w.println("# to # 100% of the target rate in a pattern that is gradual " + 498 "at first, rapid in"); 499 w.println("# the middle, and then gradual again at the end, and then " + 500 "decreases back to"); 501 w.println("# zero in a mirror image of the ascent. A graph of the " + 502 "load generated by this"); 503 w.println("# pattern resembles a sine wave, but starting at the " + 504 "lowest point in the trough"); 505 w.println("# of the wave (mathematically, represented by the function " + 506 "'y=sin(x-pi/2)+1')."); 507 w.println("#"); 508 w.println("#0.000x"); 509 w.println("#0.001x"); 510 w.println("#0.002x"); 511 w.println("#0.004x"); 512 w.println("#0.006x"); 513 w.println("#0.009x"); 514 w.println("#0.012x"); 515 w.println("#0.016x"); 516 w.println("#0.020x"); 517 w.println("#0.024x"); 518 w.println("#0.030x"); 519 w.println("#0.035x"); 520 w.println("#0.041x"); 521 w.println("#0.048x"); 522 w.println("#0.054x"); 523 w.println("#0.062x"); 524 w.println("#0.070x"); 525 w.println("#0.078x"); 526 w.println("#0.086x"); 527 w.println("#0.095x"); 528 w.println("#0.105x"); 529 w.println("#0.115x"); 530 w.println("#0.125x"); 531 w.println("#0.136x"); 532 w.println("#0.146x"); 533 w.println("#0.158x"); 534 w.println("#0.169x"); 535 w.println("#0.181x"); 536 w.println("#0.194x"); 537 w.println("#0.206x"); 538 w.println("#0.219x"); 539 w.println("#0.232x"); 540 w.println("#0.245x"); 541 w.println("#0.259x"); 542 w.println("#0.273x"); 543 w.println("#0.287x"); 544 w.println("#0.301x"); 545 w.println("#0.316x"); 546 w.println("#0.331x"); 547 w.println("#0.345x"); 548 w.println("#0.361x"); 549 w.println("#0.376x"); 550 w.println("#0.391x"); 551 w.println("#0.406x"); 552 w.println("#0.422x"); 553 w.println("#0.437x"); 554 w.println("#0.453x"); 555 w.println("#0.469x"); 556 w.println("#0.484x"); 557 w.println("#0.500x"); 558 w.println("#0.500x"); 559 w.println("#0.516x"); 560 w.println("#0.531x"); 561 w.println("#0.547x"); 562 w.println("#0.563x"); 563 w.println("#0.578x"); 564 w.println("#0.594x"); 565 w.println("#0.609x"); 566 w.println("#0.624x"); 567 w.println("#0.639x"); 568 w.println("#0.655x"); 569 w.println("#0.669x"); 570 w.println("#0.684x"); 571 w.println("#0.699x"); 572 w.println("#0.713x"); 573 w.println("#0.727x"); 574 w.println("#0.741x"); 575 w.println("#0.755x"); 576 w.println("#0.768x"); 577 w.println("#0.781x"); 578 w.println("#0.794x"); 579 w.println("#0.806x"); 580 w.println("#0.819x"); 581 w.println("#0.831x"); 582 w.println("#0.842x"); 583 w.println("#0.854x"); 584 w.println("#0.864x"); 585 w.println("#0.875x"); 586 w.println("#0.885x"); 587 w.println("#0.895x"); 588 w.println("#0.905x"); 589 w.println("#0.914x"); 590 w.println("#0.922x"); 591 w.println("#0.930x"); 592 w.println("#0.938x"); 593 w.println("#0.946x"); 594 w.println("#0.952x"); 595 w.println("#0.959x"); 596 w.println("#0.965x"); 597 w.println("#0.970x"); 598 w.println("#0.976x"); 599 w.println("#0.980x"); 600 w.println("#0.984x"); 601 w.println("#0.988x"); 602 w.println("#0.991x"); 603 w.println("#0.994x"); 604 w.println("#0.996x"); 605 w.println("#0.998x"); 606 w.println("#0.999x"); 607 w.println("#1.000x"); 608 w.println("#1.000x"); 609 w.println("#1.000x"); 610 w.println("#0.999x"); 611 w.println("#0.998x"); 612 w.println("#0.996x"); 613 w.println("#0.994x"); 614 w.println("#0.991x"); 615 w.println("#0.988x"); 616 w.println("#0.984x"); 617 w.println("#0.980x"); 618 w.println("#0.976x"); 619 w.println("#0.970x"); 620 w.println("#0.965x"); 621 w.println("#0.959x"); 622 w.println("#0.952x"); 623 w.println("#0.946x"); 624 w.println("#0.938x"); 625 w.println("#0.930x"); 626 w.println("#0.922x"); 627 w.println("#0.914x"); 628 w.println("#0.905x"); 629 w.println("#0.895x"); 630 w.println("#0.885x"); 631 w.println("#0.875x"); 632 w.println("#0.864x"); 633 w.println("#0.854x"); 634 w.println("#0.842x"); 635 w.println("#0.831x"); 636 w.println("#0.819x"); 637 w.println("#0.806x"); 638 w.println("#0.794x"); 639 w.println("#0.781x"); 640 w.println("#0.768x"); 641 w.println("#0.755x"); 642 w.println("#0.741x"); 643 w.println("#0.727x"); 644 w.println("#0.713x"); 645 w.println("#0.699x"); 646 w.println("#0.684x"); 647 w.println("#0.669x"); 648 w.println("#0.655x"); 649 w.println("#0.639x"); 650 w.println("#0.624x"); 651 w.println("#0.609x"); 652 w.println("#0.594x"); 653 w.println("#0.578x"); 654 w.println("#0.563x"); 655 w.println("#0.547x"); 656 w.println("#0.531x"); 657 w.println("#0.516x"); 658 w.println("#0.500x"); 659 w.println("#0.484x"); 660 w.println("#0.469x"); 661 w.println("#0.453x"); 662 w.println("#0.437x"); 663 w.println("#0.422x"); 664 w.println("#0.406x"); 665 w.println("#0.391x"); 666 w.println("#0.376x"); 667 w.println("#0.361x"); 668 w.println("#0.345x"); 669 w.println("#0.331x"); 670 w.println("#0.316x"); 671 w.println("#0.301x"); 672 w.println("#0.287x"); 673 w.println("#0.273x"); 674 w.println("#0.259x"); 675 w.println("#0.245x"); 676 w.println("#0.232x"); 677 w.println("#0.219x"); 678 w.println("#0.206x"); 679 w.println("#0.194x"); 680 w.println("#0.181x"); 681 w.println("#0.169x"); 682 w.println("#0.158x"); 683 w.println("#0.146x"); 684 w.println("#0.136x"); 685 w.println("#0.125x"); 686 w.println("#0.115x"); 687 w.println("#0.105x"); 688 w.println("#0.095x"); 689 w.println("#0.086x"); 690 w.println("#0.078x"); 691 w.println("#0.070x"); 692 w.println("#0.062x"); 693 w.println("#0.054x"); 694 w.println("#0.048x"); 695 w.println("#0.041x"); 696 w.println("#0.035x"); 697 w.println("#0.030x"); 698 w.println("#0.024x"); 699 w.println("#0.020x"); 700 w.println("#0.016x"); 701 w.println("#0.012x"); 702 w.println("#0.009x"); 703 w.println("#0.006x"); 704 w.println("#0.004x"); 705 w.println("#0.002x"); 706 w.println("#0.001x"); 707 w.println("#0.000x"); 708 w.println(); 709 w.println(); 710 w.println("# Example: Sawtooth Rate"); 711 w.println("#"); 712 w.println("# This pattern starts with a rate of zero operations per " + 713 "second and increases"); 714 w.println("# linearly to 100% of the target rate. A graph of the load " + 715 "generated by a"); 716 w.println("# single iteration of this pattern resembles the hypotenuse " + 717 "of a right"); 718 w.println("# triangle, and a graph of multiple iterations resembles " + 719 "the teeth of a saw"); 720 w.println("# blade."); 721 w.println("#"); 722 w.println("#0.00x"); 723 w.println("#0.01x"); 724 w.println("#0.02x"); 725 w.println("#0.03x"); 726 w.println("#0.04x"); 727 w.println("#0.05x"); 728 w.println("#0.06x"); 729 w.println("#0.07x"); 730 w.println("#0.08x"); 731 w.println("#0.09x"); 732 w.println("#0.10x"); 733 w.println("#0.11x"); 734 w.println("#0.12x"); 735 w.println("#0.13x"); 736 w.println("#0.14x"); 737 w.println("#0.15x"); 738 w.println("#0.16x"); 739 w.println("#0.17x"); 740 w.println("#0.18x"); 741 w.println("#0.19x"); 742 w.println("#0.20x"); 743 w.println("#0.21x"); 744 w.println("#0.22x"); 745 w.println("#0.23x"); 746 w.println("#0.24x"); 747 w.println("#0.25x"); 748 w.println("#0.26x"); 749 w.println("#0.27x"); 750 w.println("#0.28x"); 751 w.println("#0.29x"); 752 w.println("#0.30x"); 753 w.println("#0.31x"); 754 w.println("#0.32x"); 755 w.println("#0.33x"); 756 w.println("#0.34x"); 757 w.println("#0.35x"); 758 w.println("#0.36x"); 759 w.println("#0.37x"); 760 w.println("#0.38x"); 761 w.println("#0.39x"); 762 w.println("#0.40x"); 763 w.println("#0.41x"); 764 w.println("#0.42x"); 765 w.println("#0.43x"); 766 w.println("#0.44x"); 767 w.println("#0.45x"); 768 w.println("#0.46x"); 769 w.println("#0.47x"); 770 w.println("#0.48x"); 771 w.println("#0.49x"); 772 w.println("#0.50x"); 773 w.println("#0.51x"); 774 w.println("#0.52x"); 775 w.println("#0.53x"); 776 w.println("#0.54x"); 777 w.println("#0.55x"); 778 w.println("#0.56x"); 779 w.println("#0.57x"); 780 w.println("#0.58x"); 781 w.println("#0.59x"); 782 w.println("#0.60x"); 783 w.println("#0.61x"); 784 w.println("#0.62x"); 785 w.println("#0.63x"); 786 w.println("#0.64x"); 787 w.println("#0.65x"); 788 w.println("#0.66x"); 789 w.println("#0.67x"); 790 w.println("#0.68x"); 791 w.println("#0.69x"); 792 w.println("#0.70x"); 793 w.println("#0.71x"); 794 w.println("#0.72x"); 795 w.println("#0.73x"); 796 w.println("#0.74x"); 797 w.println("#0.75x"); 798 w.println("#0.76x"); 799 w.println("#0.77x"); 800 w.println("#0.78x"); 801 w.println("#0.79x"); 802 w.println("#0.80x"); 803 w.println("#0.81x"); 804 w.println("#0.82x"); 805 w.println("#0.83x"); 806 w.println("#0.84x"); 807 w.println("#0.85x"); 808 w.println("#0.86x"); 809 w.println("#0.87x"); 810 w.println("#0.88x"); 811 w.println("#0.89x"); 812 w.println("#0.90x"); 813 w.println("#0.91x"); 814 w.println("#0.92x"); 815 w.println("#0.93x"); 816 w.println("#0.94x"); 817 w.println("#0.95x"); 818 w.println("#0.96x"); 819 w.println("#0.97x"); 820 w.println("#0.98x"); 821 w.println("#0.99x"); 822 w.println("#1.00x"); 823 w.println(); 824 w.println(); 825 w.println("# Example: Triangle Rate"); 826 w.println("#"); 827 w.println("# This pattern starts with a rate of zero operations per " + 828 "second and increases"); 829 w.println("# linearly to 100% of the target rate before decreasing " + 830 "linearly back to 0%."); 831 w.println("# A graph of the load generated by a single iteration of " + 832 "this tool is like that"); 833 w.println("# of the sawtooth pattern above followed immediately by its " + 834 "mirror image."); 835 w.println("#"); 836 w.println("#0.00x"); 837 w.println("#0.01x"); 838 w.println("#0.02x"); 839 w.println("#0.03x"); 840 w.println("#0.04x"); 841 w.println("#0.05x"); 842 w.println("#0.06x"); 843 w.println("#0.07x"); 844 w.println("#0.08x"); 845 w.println("#0.09x"); 846 w.println("#0.10x"); 847 w.println("#0.11x"); 848 w.println("#0.12x"); 849 w.println("#0.13x"); 850 w.println("#0.14x"); 851 w.println("#0.15x"); 852 w.println("#0.16x"); 853 w.println("#0.17x"); 854 w.println("#0.18x"); 855 w.println("#0.19x"); 856 w.println("#0.20x"); 857 w.println("#0.21x"); 858 w.println("#0.22x"); 859 w.println("#0.23x"); 860 w.println("#0.24x"); 861 w.println("#0.25x"); 862 w.println("#0.26x"); 863 w.println("#0.27x"); 864 w.println("#0.28x"); 865 w.println("#0.29x"); 866 w.println("#0.30x"); 867 w.println("#0.31x"); 868 w.println("#0.32x"); 869 w.println("#0.33x"); 870 w.println("#0.34x"); 871 w.println("#0.35x"); 872 w.println("#0.36x"); 873 w.println("#0.37x"); 874 w.println("#0.38x"); 875 w.println("#0.39x"); 876 w.println("#0.40x"); 877 w.println("#0.41x"); 878 w.println("#0.42x"); 879 w.println("#0.43x"); 880 w.println("#0.44x"); 881 w.println("#0.45x"); 882 w.println("#0.46x"); 883 w.println("#0.47x"); 884 w.println("#0.48x"); 885 w.println("#0.49x"); 886 w.println("#0.50x"); 887 w.println("#0.51x"); 888 w.println("#0.52x"); 889 w.println("#0.53x"); 890 w.println("#0.54x"); 891 w.println("#0.55x"); 892 w.println("#0.56x"); 893 w.println("#0.57x"); 894 w.println("#0.58x"); 895 w.println("#0.59x"); 896 w.println("#0.60x"); 897 w.println("#0.61x"); 898 w.println("#0.62x"); 899 w.println("#0.63x"); 900 w.println("#0.64x"); 901 w.println("#0.65x"); 902 w.println("#0.66x"); 903 w.println("#0.67x"); 904 w.println("#0.68x"); 905 w.println("#0.69x"); 906 w.println("#0.70x"); 907 w.println("#0.71x"); 908 w.println("#0.72x"); 909 w.println("#0.73x"); 910 w.println("#0.74x"); 911 w.println("#0.75x"); 912 w.println("#0.76x"); 913 w.println("#0.77x"); 914 w.println("#0.78x"); 915 w.println("#0.79x"); 916 w.println("#0.80x"); 917 w.println("#0.81x"); 918 w.println("#0.82x"); 919 w.println("#0.83x"); 920 w.println("#0.84x"); 921 w.println("#0.85x"); 922 w.println("#0.86x"); 923 w.println("#0.87x"); 924 w.println("#0.88x"); 925 w.println("#0.89x"); 926 w.println("#0.90x"); 927 w.println("#0.91x"); 928 w.println("#0.92x"); 929 w.println("#0.93x"); 930 w.println("#0.94x"); 931 w.println("#0.95x"); 932 w.println("#0.96x"); 933 w.println("#0.97x"); 934 w.println("#0.98x"); 935 w.println("#0.99x"); 936 w.println("#1.00x"); 937 w.println("#0.99x"); 938 w.println("#0.98x"); 939 w.println("#0.97x"); 940 w.println("#0.96x"); 941 w.println("#0.95x"); 942 w.println("#0.94x"); 943 w.println("#0.93x"); 944 w.println("#0.92x"); 945 w.println("#0.91x"); 946 w.println("#0.90x"); 947 w.println("#0.89x"); 948 w.println("#0.88x"); 949 w.println("#0.87x"); 950 w.println("#0.86x"); 951 w.println("#0.85x"); 952 w.println("#0.84x"); 953 w.println("#0.83x"); 954 w.println("#0.82x"); 955 w.println("#0.81x"); 956 w.println("#0.80x"); 957 w.println("#0.79x"); 958 w.println("#0.78x"); 959 w.println("#0.77x"); 960 w.println("#0.76x"); 961 w.println("#0.75x"); 962 w.println("#0.74x"); 963 w.println("#0.73x"); 964 w.println("#0.72x"); 965 w.println("#0.71x"); 966 w.println("#0.70x"); 967 w.println("#0.69x"); 968 w.println("#0.68x"); 969 w.println("#0.67x"); 970 w.println("#0.66x"); 971 w.println("#0.65x"); 972 w.println("#0.64x"); 973 w.println("#0.63x"); 974 w.println("#0.62x"); 975 w.println("#0.61x"); 976 w.println("#0.60x"); 977 w.println("#0.59x"); 978 w.println("#0.58x"); 979 w.println("#0.57x"); 980 w.println("#0.56x"); 981 w.println("#0.55x"); 982 w.println("#0.54x"); 983 w.println("#0.53x"); 984 w.println("#0.52x"); 985 w.println("#0.51x"); 986 w.println("#0.50x"); 987 w.println("#0.49x"); 988 w.println("#0.48x"); 989 w.println("#0.47x"); 990 w.println("#0.46x"); 991 w.println("#0.45x"); 992 w.println("#0.44x"); 993 w.println("#0.43x"); 994 w.println("#0.42x"); 995 w.println("#0.41x"); 996 w.println("#0.40x"); 997 w.println("#0.39x"); 998 w.println("#0.38x"); 999 w.println("#0.37x"); 1000 w.println("#0.36x"); 1001 w.println("#0.35x"); 1002 w.println("#0.34x"); 1003 w.println("#0.33x"); 1004 w.println("#0.32x"); 1005 w.println("#0.31x"); 1006 w.println("#0.30x"); 1007 w.println("#0.29x"); 1008 w.println("#0.28x"); 1009 w.println("#0.27x"); 1010 w.println("#0.26x"); 1011 w.println("#0.25x"); 1012 w.println("#0.24x"); 1013 w.println("#0.23x"); 1014 w.println("#0.22x"); 1015 w.println("#0.21x"); 1016 w.println("#0.20x"); 1017 w.println("#0.19x"); 1018 w.println("#0.18x"); 1019 w.println("#0.17x"); 1020 w.println("#0.16x"); 1021 w.println("#0.15x"); 1022 w.println("#0.14x"); 1023 w.println("#0.13x"); 1024 w.println("#0.12x"); 1025 w.println("#0.11x"); 1026 w.println("#0.10x"); 1027 w.println("#0.09x"); 1028 w.println("#0.08x"); 1029 w.println("#0.07x"); 1030 w.println("#0.06x"); 1031 w.println("#0.05x"); 1032 w.println("#0.04x"); 1033 w.println("#0.03x"); 1034 w.println("#0.02x"); 1035 w.println("#0.01x"); 1036 w.println("#0.00x"); 1037 w.println(); 1038 w.println(); 1039 w.println("# Example: 'Hockey Stick' Rate"); 1040 w.println("#"); 1041 w.println("# This pattern starts with a rate of zero operations per " + 1042 "second and increases"); 1043 w.println("# slowly at first before ramping up much more quickly. A " + 1044 "graph of the load"); 1045 w.println("# generated by a single iteration of this pattern vaguely " + 1046 "resembles a hockey"); 1047 w.println("# stick."); 1048 w.println("#"); 1049 w.println("#0.000x"); 1050 w.println("#0.000x"); 1051 w.println("#0.000x"); 1052 w.println("#0.000x"); 1053 w.println("#0.000x"); 1054 w.println("#0.000x"); 1055 w.println("#0.000x"); 1056 w.println("#0.000x"); 1057 w.println("#0.001x"); 1058 w.println("#0.001x"); 1059 w.println("#0.001x"); 1060 w.println("#0.001x"); 1061 w.println("#0.002x"); 1062 w.println("#0.002x"); 1063 w.println("#0.003x"); 1064 w.println("#0.003x"); 1065 w.println("#0.004x"); 1066 w.println("#0.005x"); 1067 w.println("#0.006x"); 1068 w.println("#0.007x"); 1069 w.println("#0.008x"); 1070 w.println("#0.009x"); 1071 w.println("#0.011x"); 1072 w.println("#0.012x"); 1073 w.println("#0.014x"); 1074 w.println("#0.016x"); 1075 w.println("#0.018x"); 1076 w.println("#0.020x"); 1077 w.println("#0.022x"); 1078 w.println("#0.024x"); 1079 w.println("#0.027x"); 1080 w.println("#0.030x"); 1081 w.println("#0.033x"); 1082 w.println("#0.036x"); 1083 w.println("#0.039x"); 1084 w.println("#0.043x"); 1085 w.println("#0.047x"); 1086 w.println("#0.051x"); 1087 w.println("#0.055x"); 1088 w.println("#0.059x"); 1089 w.println("#0.064x"); 1090 w.println("#0.069x"); 1091 w.println("#0.074x"); 1092 w.println("#0.080x"); 1093 w.println("#0.085x"); 1094 w.println("#0.091x"); 1095 w.println("#0.097x"); 1096 w.println("#0.104x"); 1097 w.println("#0.111x"); 1098 w.println("#0.118x"); 1099 w.println("#0.125x"); 1100 w.println("#0.133x"); 1101 w.println("#0.141x"); 1102 w.println("#0.149x"); 1103 w.println("#0.157x"); 1104 w.println("#0.166x"); 1105 w.println("#0.176x"); 1106 w.println("#0.185x"); 1107 w.println("#0.195x"); 1108 w.println("#0.205x"); 1109 w.println("#0.216x"); 1110 w.println("#0.227x"); 1111 w.println("#0.238x"); 1112 w.println("#0.250x"); 1113 w.println("#0.262x"); 1114 w.println("#0.275x"); 1115 w.println("#0.287x"); 1116 w.println("#0.301x"); 1117 w.println("#0.314x"); 1118 w.println("#0.329x"); 1119 w.println("#0.343x"); 1120 w.println("#0.358x"); 1121 w.println("#0.373x"); 1122 w.println("#0.389x"); 1123 w.println("#0.405x"); 1124 w.println("#0.422x"); 1125 w.println("#0.439x"); 1126 w.println("#0.457x"); 1127 w.println("#0.475x"); 1128 w.println("#0.493x"); 1129 w.println("#0.512x"); 1130 w.println("#0.531x"); 1131 w.println("#0.551x"); 1132 w.println("#0.572x"); 1133 w.println("#0.593x"); 1134 w.println("#0.614x"); 1135 w.println("#0.636x"); 1136 w.println("#0.659x"); 1137 w.println("#0.681x"); 1138 w.println("#0.705x"); 1139 w.println("#0.729x"); 1140 w.println("#0.754x"); 1141 w.println("#0.779x"); 1142 w.println("#0.804x"); 1143 w.println("#0.831x"); 1144 w.println("#0.857x"); 1145 w.println("#0.885x"); 1146 w.println("#0.913x"); 1147 w.println("#0.941x"); 1148 w.println("#0.970x"); 1149 w.println("#1.000x"); 1150 w.println(); 1151 } 1152 finally 1153 { 1154 w.close(); 1155 } 1156 } 1157 1158 1159 1160 /** 1161 * Constructs a new RateAdjustor with the specified parameters. See the 1162 * class-level javadoc for more information. 1163 * 1164 * @param barrier The barrier to update based on the specified 1165 * rates. 1166 * @param baseRatePerSecond The baseline rate per second, or 0 if none was 1167 * specified. 1168 * @param rates A list of rates and durations as described in 1169 * the class-level javadoc. The reader will 1170 * always be closed before this method returns. 1171 * 1172 * @throws IOException If there is a problem reading from 1173 * the rates Reader. 1174 * @throws IllegalArgumentException If there is a problem with the rates 1175 * input. 1176 */ 1177 public RateAdjustor(@NotNull final FixedRateBarrier barrier, 1178 final long baseRatePerSecond, 1179 @NotNull final Reader rates) 1180 throws IOException, IllegalArgumentException 1181 { 1182 // Read the header first. 1183 final List<String> lines; 1184 try 1185 { 1186 Validator.ensureNotNull(barrier, rates); 1187 setDaemon(true); 1188 this.barrier = barrier; 1189 1190 lines = readLines(rates); 1191 } 1192 finally 1193 { 1194 rates.close(); 1195 } 1196 1197 final Map<String,String> header = consumeHeader(lines); 1198 1199 final Set<String> invalidKeys = new LinkedHashSet<>(header.keySet()); 1200 invalidKeys.removeAll(KEYS); 1201 if (! invalidKeys.isEmpty()) 1202 { 1203 throw new IllegalArgumentException( 1204 ERR_RATE_ADJUSTOR_INVALID_KEYS.get(invalidKeys, KEYS)); 1205 } 1206 1207 final String format = header.get(FORMAT_KEY); 1208 if (format == null) 1209 { 1210 throw new IllegalArgumentException(ERR_RATE_ADJUSTOR_MISSING_FORMAT.get( 1211 FORMAT_KEY, FORMATS, COMMENT_START)); 1212 } 1213 1214 if (! format.equals(FORMAT_VALUE_RATE_DURATION)) 1215 { 1216 // For now this is the only format that we support. 1217 throw new IllegalArgumentException( 1218 ERR_RATE_ADJUSTOR_INVALID_FORMAT.get(format, FORMAT_KEY, FORMATS)); 1219 } 1220 1221 repeat = Boolean.parseBoolean(header.get(REPEAT_KEY)); 1222 1223 // This will be non-zero if it's set in the input. 1224 long defaultDurationMillis = 0; 1225 final String defaultDurationStr = header.get(DEFAULT_DURATION_KEY); 1226 if (defaultDurationStr != null) 1227 { 1228 try 1229 { 1230 defaultDurationMillis = DurationArgument.parseDuration( 1231 defaultDurationStr, TimeUnit.MILLISECONDS); 1232 } 1233 catch (final ArgumentException e) 1234 { 1235 Debug.debugException(e); 1236 throw new IllegalArgumentException( 1237 ERR_RATE_ADJUSTOR_INVALID_DEFAULT_DURATION.get( 1238 defaultDurationStr, e.getExceptionMessage()), 1239 e); 1240 } 1241 } 1242 1243 // Now parse out the rates and durations, which will look like this: 1244 // 1000,1s 1245 // 1.5,1d 1246 // 0.5X, 1m 1247 // # Duration can be omitted if default-duration header was included. 1248 // 1000 1249 final List<ObjectPair<Double,Long>> ratesAndDurationList = 1250 new ArrayList<>(10); 1251 final Pattern splitPattern = Pattern.compile("\\s*,\\s*"); 1252 for (final String fullLine: lines) 1253 { 1254 // Strip out comments and white space. 1255 String line = fullLine; 1256 final int commentStart = fullLine.indexOf(COMMENT_START); 1257 if (commentStart >= 0) 1258 { 1259 line = line.substring(0, commentStart); 1260 } 1261 line = line.trim(); 1262 1263 if (line.isEmpty()) 1264 { 1265 continue; 1266 } 1267 1268 final String[] fields = splitPattern.split(line); 1269 if (!((fields.length == 2) || 1270 ((fields.length == 1) && defaultDurationMillis != 0))) 1271 { 1272 throw new IllegalArgumentException(ERR_RATE_ADJUSTOR_INVALID_LINE.get( 1273 fullLine, DEFAULT_DURATION_KEY)); 1274 } 1275 1276 String rateStr = fields[0]; 1277 1278 boolean isRateMultiplier = false; 1279 if (rateStr.endsWith("X") || rateStr.endsWith("x")) 1280 { 1281 rateStr = rateStr.substring(0, rateStr.length() - 1).trim(); 1282 isRateMultiplier = true; 1283 } 1284 1285 double rate; 1286 try 1287 { 1288 rate = Double.parseDouble(rateStr); 1289 } 1290 catch (final NumberFormatException e) 1291 { 1292 Debug.debugException(e); 1293 throw new IllegalArgumentException( 1294 ERR_RATE_ADJUSTOR_INVALID_RATE.get(rateStr, fullLine), e); 1295 } 1296 1297 // Values that look like 2X are a multiplier on the base rate. 1298 if (isRateMultiplier) 1299 { 1300 if (baseRatePerSecond <= 0) 1301 { 1302 throw new IllegalArgumentException( 1303 ERR_RATE_ADJUSTOR_RELATIVE_RATE_WITHOUT_BASELINE.get( 1304 rateStr, fullLine)); 1305 } 1306 1307 rate *= baseRatePerSecond; 1308 } 1309 1310 final long durationMillis; 1311 if (fields.length < 2) 1312 { 1313 durationMillis = defaultDurationMillis; 1314 } 1315 else 1316 { 1317 final String duration = fields[1]; 1318 try 1319 { 1320 durationMillis = DurationArgument.parseDuration( 1321 duration, TimeUnit.MILLISECONDS); 1322 } 1323 catch (final ArgumentException e) 1324 { 1325 Debug.debugException(e); 1326 throw new IllegalArgumentException( 1327 ERR_RATE_ADJUSTOR_INVALID_DURATION.get(duration, fullLine, 1328 e.getExceptionMessage()), 1329 e); 1330 } 1331 } 1332 1333 ratesAndDurationList.add(new ObjectPair<>(rate, durationMillis)); 1334 } 1335 ratesAndDurations = Collections.unmodifiableList(ratesAndDurationList); 1336 } 1337 1338 1339 1340 /** 1341 * Starts this thread and waits for the initial rate to be set. 1342 */ 1343 @Override 1344 public void start() 1345 { 1346 super.start(); 1347 1348 // Wait until the initial rate is set. Assuming the caller starts this 1349 // RateAdjustor before the FixedRateBarrier is used by other threads, 1350 // this will guarantee that the initial rate is in place before the 1351 // barrier is used. 1352 try 1353 { 1354 initialRateSetLatch.await(); 1355 } 1356 catch (final InterruptedException e) 1357 { 1358 Debug.debugException(e); 1359 Thread.currentThread().interrupt(); 1360 } 1361 } 1362 1363 1364 1365 /** 1366 * Adjusts the rate in FixedRateBarrier as described in the rates. 1367 */ 1368 @Override 1369 public void run() 1370 { 1371 try 1372 { 1373 if (ratesAndDurations.isEmpty()) 1374 { 1375 return; 1376 } 1377 1378 do 1379 { 1380 final List<ObjectPair<Double,Long>> ratesAndEndTimes = 1381 new ArrayList<>(ratesAndDurations.size()); 1382 long endTime = System.currentTimeMillis(); 1383 for (final ObjectPair<Double,Long> rateAndDuration : ratesAndDurations) 1384 { 1385 endTime += rateAndDuration.getSecond(); 1386 ratesAndEndTimes.add(new ObjectPair<>(rateAndDuration.getFirst(), 1387 endTime)); 1388 } 1389 1390 for (final ObjectPair<Double,Long> rateAndEndTime: ratesAndEndTimes) 1391 { 1392 if (shutDown) 1393 { 1394 return; 1395 } 1396 1397 final double rate = rateAndEndTime.getFirst(); 1398 final long intervalMillis = barrier.getTargetRate().getFirst(); 1399 final int perInterval = calculatePerInterval(intervalMillis, rate); 1400 1401 barrier.setRate(intervalMillis, perInterval); 1402 1403 // Signal start() that we've set the initial rate. 1404 if (initialRateSetLatch.getCount() > 0) 1405 { 1406 initialRateSetLatch.countDown(); 1407 } 1408 1409 // Hold at this rate for the specified duration. 1410 final long durationMillis = 1411 rateAndEndTime.getSecond() - System.currentTimeMillis(); 1412 if (durationMillis > 0L) 1413 { 1414 sleeper.sleep(durationMillis); 1415 } 1416 } 1417 } 1418 while (repeat); 1419 } 1420 finally 1421 { 1422 // Just in case we happened to be shutdown before we were started. 1423 // We still want start() to be able to return. 1424 if (initialRateSetLatch.getCount() > 0) 1425 { 1426 initialRateSetLatch.countDown(); 1427 } 1428 } 1429 } 1430 1431 1432 1433 /** 1434 * Signals this to shut down. 1435 */ 1436 public void shutDown() 1437 { 1438 shutDown = true; 1439 sleeper.wakeup(); 1440 } 1441 1442 1443 1444 /** 1445 * Returns the of rates and durations. This is primarily here for testing 1446 * purposes. 1447 * 1448 * @return The list of rates and durations. 1449 */ 1450 @NotNull() 1451 List<ObjectPair<Double,Long>> getRatesAndDurations() 1452 { 1453 return ratesAndDurations; 1454 } 1455 1456 1457 1458 /** 1459 * Calculates the rate per interval given the specified interval width 1460 * and the target rate per second. (This is static and non-private so that 1461 * it can be unit tested.) 1462 * 1463 * @param intervalDurationMillis The duration of the interval in 1464 * milliseconds. 1465 * @param ratePerSecond The target rate per second. 1466 * 1467 * @return The rate per interval, which will be at least 1. 1468 */ 1469 static int calculatePerInterval(final long intervalDurationMillis, 1470 final double ratePerSecond) 1471 { 1472 final double intervalDurationSeconds = intervalDurationMillis / 1000.0; 1473 final double ratePerInterval = ratePerSecond * intervalDurationSeconds; 1474 return (int)Math.max(1, Math.round(ratePerInterval)); 1475 } 1476 1477 1478 1479 /** 1480 * This reads the header at the start of the file. All blank lines and 1481 * comment lines will be ignored. The end of the header will be signified by 1482 * a line containing only the text "END HEADER". All non-blank, non-comment 1483 * lines in the header must be in the format "name=value", where there may be 1484 * zero or more spaces on either side of the equal sign, the name must not 1485 * contain either the space or the equal sign character, and the value must 1486 * not begin or end with a space. Header lines must not contain partial-line 1487 * comments. 1488 * 1489 * @param lines The lines of input that include the header. 1490 * 1491 * @return A map of key/value pairs extracted from the header. 1492 * 1493 * @throws IllegalArgumentException If a problem is encountered while 1494 * parsing the header (e.g., a malformed 1495 * header line is encountered, multiple 1496 * headers have the same key, there is no 1497 * end of header marker, etc.). 1498 */ 1499 @NotNull() 1500 static Map<String,String> consumeHeader(@NotNull final List<String> lines) 1501 throws IllegalArgumentException 1502 { 1503 // The header will look like this: 1504 // key1=value1 1505 // key2 = value2 1506 // END HEADER 1507 boolean endHeaderFound = false; 1508 final Map<String,String> headerMap = new 1509 LinkedHashMap<>(StaticUtils.computeMapCapacity(3)); 1510 final Iterator<String> lineIter = lines.iterator(); 1511 while (lineIter.hasNext()) 1512 { 1513 final String line = lineIter.next().trim(); 1514 lineIter.remove(); 1515 1516 if (line.isEmpty() || line.startsWith(String.valueOf(COMMENT_START))) 1517 { 1518 continue; 1519 } 1520 1521 if (line.equalsIgnoreCase(END_HEADER_TEXT)) 1522 { 1523 endHeaderFound = true; 1524 break; 1525 } 1526 1527 final int equalPos = line.indexOf('='); 1528 if (equalPos < 0) 1529 { 1530 throw new IllegalArgumentException( 1531 ERR_RATE_ADJUSTOR_HEADER_NO_EQUAL.get(line)); 1532 } 1533 1534 final String key = line.substring(0, equalPos).trim(); 1535 if (key.isEmpty()) 1536 { 1537 throw new IllegalArgumentException( 1538 ERR_RATE_ADJUSTOR_HEADER_EMPTY_KEY.get(line)); 1539 } 1540 1541 final String newValue = line.substring(equalPos+1).trim(); 1542 final String existingValue = headerMap.get(key); 1543 if (existingValue != null) 1544 { 1545 throw new IllegalArgumentException( 1546 ERR_RATE_ADJUSTOR_DUPLICATE_HEADER_KEY.get(key, existingValue, 1547 newValue)); 1548 } 1549 1550 headerMap.put(key, newValue); 1551 } 1552 1553 if (! endHeaderFound) 1554 { 1555 // This means we iterated across all lines without finding the end header 1556 // marker. 1557 throw new IllegalArgumentException( 1558 ERR_RATE_ADJUSTOR_NO_END_HEADER_FOUND.get(END_HEADER_TEXT)); 1559 } 1560 1561 return headerMap; 1562 } 1563 1564 1565 1566 /** 1567 * Returns a list of the lines read from the specified Reader. 1568 * 1569 * @param reader The Reader to read from. 1570 * 1571 * @return A list of the lines read from the specified Reader. 1572 * 1573 * @throws IOException If there is a problem reading from the Reader. 1574 */ 1575 @NotNull() 1576 private static List<String> readLines(@NotNull final Reader reader) 1577 throws IOException 1578 { 1579 final BufferedReader bufferedReader = new BufferedReader(reader); 1580 1581 // We remove items from the front of the list, so a linked list works best. 1582 final List<String> lines = new LinkedList<>(); 1583 1584 String line; 1585 while ((line = bufferedReader.readLine()) != null) 1586 { 1587 lines.add(line); 1588 } 1589 1590 return lines; 1591 } 1592} 1593