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