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