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