[
  {
    "path": ".gitignore",
    "content": "*.class\n*.jar\n!gradle/wrapper/gradle-wrapper.jar\n*.war\n*.ear\nhs_err_pid*\n.gradle\nbuild/\ngradle-app.setting\n.idea\njbender.iml\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: java\njdk:\n  - oraclejdk8\nbefore_cache:\n  - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock\ncache:\n  directories:\n    - $HOME/.gradle/caches/\n    - $HOME/.gradle/wrapper/\n"
  },
  {
    "path": "LICENSE",
    "content": "Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"{}\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright {yyyy} {name of copyright owner}\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License.\n"
  },
  {
    "path": "README.md",
    "content": "JBender\n=======\n\n[![Build Status](https://travis-ci.org/pinterest/jbender.svg)](https://travis-ci.org/pinterest/jbender)\n\nJBender makes it easy to build load testers for services using protocols like HTTP and Thrift (and\nmany others). JBender provides a library of flexible, easy-to-use primitives that can be combined\n(with plain Java code) to build high performance load testers customized to any use case, and that\ncan evolve with your service over time.\n\nJBender provides two different approaches to load testing. The first, `JBender.loadTestThroughput`\ngives the tester control over the throughput (queries per second), but not over the concurrency\n(number of active connections). This approach is well suited for services that are open to the\nInternet, like web services, and the backend services to which they speak. The primary benefit of\nthis approach is that the load tester will maintain the requested throughput, even if the service\nis struggling (or failing) to support it. As a result, a much more clear picture of how the target\nservice responds to load is provided.\n\nThe second approach, `JBender.loadTestConcurrency`, gives the tester control over the concurrency\n(number of active connections), but not the throughput (queries per second). This approach is well\nsuited to applications that need to test a large number of concurrent, inactive connections, like\nchat servers. This approach is not suitable for testing request latency, as the load tester will\nslow down to match the server (because it cannot start more connections than requested).\n\nThat JBender is a library makes it flexible and easy to extend, but means it takes longer to create\nan initial load tester. As a result, we've focused on creating easy-to-follow tutorials.\n\n## Quasar\n\nThe JBender library makes heavy use of [Quasar](http://www.paralleluniverse.co/quasar/), a library from [Parallel Universe](http://www.paralleluniverse.co/) which adds lightweight threads (called \"fibers\") to the JVM.\n\nThe tutorials, linked below, will help you get up and running with Quasar and JBender, and provide some background on how Quasar works (hint: it's very straightforward to use Quasar!). Here are some links for more information:\n\n* [Quasar's Documentation](http://docs.paralleluniverse.co/quasar/).\n* [Quasar's Github Page](https://github.com/puniverse/quasar).\n* [The Parallel Universe Blog](http://blog.paralleluniverse.co/).\n\n## Getting Started\n\nThe easiest way to get started with JBender is to use one of the tutorials:\n\n* [Thrift](https://github.com/pinterest/jbender/blob/master/doc/thrift/TUTORIAL.md)\n* [HTTP](https://github.com/pinterest/jbender/blob/master/doc/http/TUTORIAL.md)\n\n## Performance\n\nThe Linux TCP stack for a default server installation is usually not tuned to high\nthroughput servers or load testers. After some experimentation, we have settled on adding these\nlines to `/etc/sysctl.conf`, after which you can run `sysctl -p` to load them (although it is\nrecommended to restart your host at this point to make sure these take effect).\n\n```\n# /etc/sysctl.conf\n# Increase system file descriptor limit\nfs.file-max = 100000\n\n# Increase ephermeral IP ports\nnet.ipv4.ip_local_port_range = 1024 65000\n\n# Increase Linux autotuning TCP buffer limits\n# Set max to 16MB for 1GE and 32M (33554432) or 54M (56623104) for 10GE\n# Don't set tcp_mem itself! Let the kernel scale it based on RAM.\nnet.core.rmem_max = 16777216\nnet.core.wmem_max = 16777216\nnet.core.rmem_default = 16777216\nnet.core.wmem_default = 16777216\nnet.core.optmem_max = 40960\nnet.ipv4.tcp_rmem = 4096 87380 16777216\nnet.ipv4.tcp_wmem = 4096 65536 16777216\n\n# Make room for more TIME_WAIT sockets due to more clients,\n# and allow them to be reused if we run out of sockets\n# Also increase the max packet backlog\nnet.core.netdev_max_backlog = 100000\nnet.ipv4.tcp_max_syn_backlog = 100000\nnet.ipv4.tcp_max_tw_buckets = 2000000\nnet.ipv4.tcp_tw_reuse = 1\nnet.ipv4.tcp_tw_recycle = 1\nnet.ipv4.tcp_fin_timeout = 10\n\n# Disable TCP slow start on idle connections\nnet.ipv4.tcp_slow_start_after_idle = 0\n\n# From https://people.redhat.com/alikins/system_tuning.html\nnet.ipv4.tcp_sack = 0\nnet.ipv4.tcp_timestamps = 1\n```\n\nThis is a slightly modified version of advice taken from this source:\nhttp://www.nateware.com/linux-network-tuning-for-2013.html#.VBjahC5dVyE\n\nIn addition, it helps to increase the open file limit with something like:\n\n```ulimit -n 100000```\n\n## What Is Missing\n\nJBender does not provide any support for sending load from more than one machine. If you need to\nsend more load than a single machine can handle, or you need the requests to come from multiple\nphysical hosts (or different networks, or whatever), you currently have to write your own tools. In\naddition, the histogram implementation used by JBender is inefficient to send over the network,\nunlike q-digest or t-digest, which we hope to implement in the future.\n\nJBender does not provide any visualization tools, and has a relatively simple set of measurements,\nincluding a customizable histogram of latencies, an error rate and some other summary statistics.\nJBender does provide a complete log of everything that happens during a load test, so you can use\nexisting tools to graph any aspect of that data, but nothing in JBender makes that easier right now.\n\nJBender only provides helper functions for HTTP and Thrift currently, because that is all we use\ninternally at Pinterest.\n\nThe load testers we have written internally with JBender have a lot of common command line arguments,\nbut we haven't finalized a set to share as part of the library.\n\n## Comparison to Other Load Testers\n\n#### Bender\n\nJBender is a port of Bender to the JVM platform with [Quasar](http://docs.paralleluniverse.co/quasar/)\nlightweight threads (_fibers_) and channels.\n\n#### JMeter\n\nJMeter provides a GUI to configure and run load tests, and can also be configured via XML (really,\nreally not recommended by hand!) and run from the command line. JMeter's is not a good approach to\nload testing services (see the JBender docs and the Iago philosophy for more details on why that is).\nIt isn't easy to extend JMeter to handle new protocols, so it doesn't have support for Thrift or\nProtobuf. It is relatively easy to extend other parts of JMeter by writing Java code, however, and\nthe GUI makes it easy to plug all the pieces together.\n\n#### Iago\n\nIago is Twitter's load testing library and it is the inspiration for JBender's `loadTestThroughput`\nfunction. Iago is a Scala library written on top of Netty and the Twitter Finagle libraries. As a\nresult, Iago is powerful, but difficult to understand, extend and configure. It was frustration with\nmaking Iago work that led to the creation of JBender.\n\n#### The Grinder\n\nThe Grinder has the same load testing approach as JMeter, but allows scripting via Jython, which\nmakes it more flexible and extensible. The Grinder uses threads, which limits the concurrency at\nwhich it can work, and makes it hard to implement things like JBender's `loadTestThroughput` function.\nThe Grinder does have support for conveniently running distributed load tests.\n\n## Copyright\n\nCopyright 2015 Pinterest.com\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n\n## Attribution\n\nJBender includes open source from the following sources:\n\n* Apache Thrift Libraries. Copyright 2014 Apache Software Foundation. Licensed under the Apache License v2.0 (http://www.apache.org/licenses/).\n* Quasar Libraries. Copyright 2014 Parallel Universe. Licensed under the GNU Lesser General Public License (http://www.gnu.org/licenses/lgpl.html).\n"
  },
  {
    "path": "build.gradle",
    "content": "plugins {\n    id 'me.champeau.gradle.jmh' version '0.2.0'\n}\n\napply plugin: 'java'\napply plugin: 'maven'\napply plugin: 'me.champeau.gradle.jmh'\n\nsourceCompatibility = 1.8\ntargetCompatibility = 1.8\n\ngroup = 'com.pinterest'\nversion = '1.0.1-SNAPSHOT'\n\next.jmhVer      = '1.13'\next.hdrHistVer  = '2.1.9'\next.quasarVer   = '0.7.5'\next.comsatVer   = '0.7.0'\next.httpCoreVer = '4.4.5'\next.slf4jVer    = '1.7.21'\next.junitVer    = '4.12'\n\nrepositories {\n    // mavenLocal()\n    mavenCentral()\n    maven {\n        url \"https://oss.sonatype.org/content/repositories/snapshots\"\n    }\n}\n\nconfigurations {\n    quasar\n}\n\nconfigurations.all {\n    resolutionStrategy {\n        failOnVersionConflict()\n\n        force \"org.hdrhistogram:HdrHistogram:$hdrHistVer\"\n        force \"org.apache.httpcomponents:httpcore:$httpCoreVer\"\n    }\n}\n\ndependencies {\n    testCompile group: 'junit', name: 'junit', version: \"$junitVer\"\n    compile group: 'co.paralleluniverse', name: 'quasar-core', version: \"$quasarVer\", classifier: 'jdk8'\n    compile group: 'co.paralleluniverse', name: 'comsat-httpclient', version: \"$comsatVer\"\n    compile group: 'org.hdrhistogram', name: 'HdrHistogram', version: \"$hdrHistVer\"\n    compile group: 'org.slf4j', name: 'slf4j-api', version: \"$slf4jVer\"\n    // compile group: 'org.slf4j', name: 'slf4j-simple', version: \"$slf4jVer\"\n\n    // For the IDE\n    // compile \"org.openjdk.jmh:jmh-core:$jmhVer\"\n    // compile \"org.openjdk.jmh:jmh-generator-annprocess:$jmhVer\"\n\n    quasar group: 'co.paralleluniverse', name: 'quasar-core', version: \"$quasarVer\", classifier: 'jdk8'\n}\n\njmh {\n    jmhVersion = \"$jmhVer\"\n    include = '.*'\n    jvmArgs = \"-server -XX:+TieredCompilation -XX:+AggressiveOpts -javaagent:${configurations.quasar.iterator().next()} -Dco.paralleluniverse.fibers.detectRunawayFibers=false\"\n    benchmarkMode = 'avgt' // 'thrpt'\n    timeUnit = 'ms'\n}\n\ntasks.withType(Test) {\n    allJvmArgs = []\n    useJUnit()\n\n    jvmArgs \"-javaagent:${configurations.quasar.iterator().next()}\"\n}\n"
  },
  {
    "path": "doc/http/TUTORIAL.md",
    "content": "JBender HTTP Tutorial\n=====================\n\nThis tutorial walks through the steps to create an HTTP load tester with JBender on a pre-built HTTP server.\n\n### Getting Started\n\nJBender uses [Gradle](http://gradle.org) as a build tool and we're going to use it for our sample load tester as well, so make sure you have it installed. The easiest way to get it on Mac OS X is probably to install [HomeBrew](http://brew.sh/) and then `brew install gradle`, while on Linux there's [LinuxBrew](https://github.com/Homebrew/linuxbrew). Your specific Linux distribution could offer native Gradle packages but they tend to lag behind the most recent version, so it's probably better to brew anyway.\n\nWe load test a ready-made server available as the [Comsat Gradle template](https://github.com/puniverse/comsat-gradle-template), so you can just `git clone` it and then run it in a separate terminal window with `gradle wrapper` followed by `./gradlew -Penv=dropwizard run` from the project directory.\n\nWriting a JBender HTTP load test involves using JBender's `FiberApacheHttpClientRequestExecutor`, which is based on [Comsat HTTP client](http://docs.paralleluniverse.co/comsat/#http-clients).\n\n### Creating the load test Gradle project\n\nIn your usual sources work root create a `jbender-http-tutorial` directory and view the following `build.gradle` file in it (you won't need to add this, it is already there):\n\n``` groovy\nplugins {\n    id \"us.kirchmeier.capsule\" version \"1.0-rc1\"\n}\n\napply plugin: 'java'\n\n// Target JDK8\nsourceCompatibility = 1.8\ntargetCompatibility = 1.8\n\ngroup = 'jbendertut'\nversion = '0.1-SNAPSHOT'\n\n// UTF8 encoding for sources\n[compileJava, compileTestJava]*.options*.encoding = \"UTF-8\"\n\nrepositories {\n    // Enable this if you want to use locally-built artifacts, e.g. if you have installed jbender locally\n    mavenLocal()\n\n    // This allows using published Quasar snapshots\n    maven {\n        url \"https://oss.sonatype.org/content/repositories/snapshots\"\n    }\n\n    mavenCentral()\n}\n\nconfigurations {\n    quasar\n}\n\ndependencies {\n    // Quasar API\n    compile group: \"co.paralleluniverse\", name: \"quasar-core\", version: \"0.6.3-SNAPSHOT\", classifier: \"jdk8\"\n\n    // Comsat HTTP Client\n    compile group: \"co.paralleluniverse\", name: \"comsat-httpclient\", version: \"0.3.0\"\n\n    // HDR histogram\n    compile group: 'org.hdrhistogram', name: 'HdrHistogram', version: \"2.1.4\"\n\n    // JBender API\n    compile group: \"com.pinterest\", name: \"jbender\", version: \"1.0\"\n\n    // Logging\n    compile group: \"org.slf4j\", name: \"slf4j-api\", version: \"1.7.12\"\n    compile group: \"org.slf4j\", name: \"slf4j-simple\", version: \"1.7.12\"\n\n    // Useful to point to the Quasar agent later in JVM flags (and Capsule-building task)\n    quasar group: \"co.paralleluniverse\", name: \"quasar-core\", version: \"0.6.3-SNAPSHOT\", classifier: \"jdk8\"\n}\n\n// Task building an handy self-contained load test capsule\ntask capsule(type: FatCapsule) {\n    applicationClass \"LoadTest\"\n\n    capsuleManifest {\n        javaAgents = [configurations.quasar.iterator().next().getName()] // Add \"=vdc\" to the Quasar agent to trace instrumentation\n        jvmArgs = [\"-server\", \"-XX:+TieredCompilation\", \"-XX:+AggressiveOpts\"] // Aggressive optimizations\n    }\n}\n\n// Gradle JavaExec load test task\ntask runLoadTest(type: JavaExec) {\n    main = \"LoadTest\"\n\n    classpath = sourceSets.main.runtimeClasspath\n\n    // Aggressive optimizations and Quasar agent\n    jvmArgs = [\"-server\", \"-XX:+TieredCompilation\", \"-XX:+AggressiveOpts\", \"-javaagent:${configurations.quasar.iterator().next()}\"] // Add \"=vdc\" to the Quasar agent to trace instrumentation\n\n    // Enable this to troubleshoot instrumentation issues\n    // systemProperties = [\"co.paralleluniverse.fibers.verifyInstrumentation\" : \"true\"]\n}\n```\n\nThis uses [Capsule](https://github.com/puniverse/capsule) to package the load tester, and provides a convenience task named `runLoadTest` that makes it easy to run the load test with gradle (in development). This is an example of what a production build file would look like for a load tester, and we highly recommend the use of Capsule!\n\n## Load Testing\n\nLet's now write a simple load tester with JBender. The next few sections walk through the various parts of the load tester. If you are in a hurry skip to the section \"Final Load Tester Program\" and just follow the instructions from there. The sample code you copied in the first step already has all this code, so these sections just describe what that code does, and why.\n\nJBender has a very simple loop in which it does the following:\n\n1. Generate an interval (in nanoseconds) and sleep for that amount of time (you have control over the length of these intervals).\n2. Fetch the next request from a channel of requests (created by you).\n3. Spawn a lightweight thread (fiber) to send the request and wait for the response (and then generate timing information).\n4. Repeat until there are no more requests to send.\n\n### Intervals\n\nThe first thing we need is a function to generate intervals (in nanoseconds) between executing\nrequests. The JBender library comes with some predefined intervals: a uniform distribution\n(always wait the same amount of time between each request) and an exponential distribution. In this\ncase we will use the exponential distribution, which means our server will experience load as\ngenerated by a [Poisson process](http://en.wikipedia.org/wiki/Poisson_process), which is fairly\ntypical of server workloads on the Internet (with the usual caveats that every service is a special\nsnowflake, etc, etc). We get the interval function with this code:\n\n``` java\nfinal IntervalGenerator intervalGenerator = new ConstantIntervalGenerator(qps);\n```\n\nWhere `qps` is our desired throughput measured in queries per second. It is also the reciprocal of\nthe mean value of the exponential distribution used to generate the request arrival times (see the\nwikipedia article above). In practice this means you will see an average QPS that fluctuates around\nthe target QPS (with less fluctuation as you increase the time interval over which you are\naveraging).\n\n### Request Generator\n\nThe second thing we need is a channel of requests to send to the HTTP server. When an interval has\nbeen generated and JBender is ready to send the request, it pulls the next request from this channel\nand spawns a Quasar _fiber_ (lightweight thread) to send the request to the server. This code creates\nand starts a simple synthetic Apache HTTP Client's `HttpGet` request generator to the \"Hello World\"\nserver endpoint:\n\n``` java\nnew Fiber<Void>(\"message-producer\", () -> {\n  // Bench handling 10k reqs\n  for (int i = 0; i < 10000; ++i) {\n    requestCh.send(new HttpGet(\"http://localhost:8080/hello-world\"));\n  }\n\n  requestCh.close();\n  return null;\n}).start();\n```\n\nNote that this loop will send 10k requests and then close the channel. Closing the channel notifies the load tester that there will be no more requests, so it can finish waiting for pending requests and then shut itself down. If you fail to close the channel the load tester will wait for a next request forever.\n\n### Request Executor\n\nThe next thing we need is a request executor, which takes the requests generated above and sends\nthem to the service. We will just use JBender's pre-built one and add a response validator:\n\n```\nfinal FiberApacheHttpClientRequestExecutor requestExecutor =\n  new FiberApacheHttpClientRequestExecutor<>((res) -> {\n    if (res == null) {\n      throw new AssertionError(\"Response is null\");\n    }\n    final int status = res.getStatusLine().getStatusCode();\n    if (status != 200) {\n      throw new AssertionError(\"Status \" + status + \" is not 200\");\n    }\n  }, 1000000);\n```\n\nThis validates that the response has actually been produced ans has a HTTP 200 status code.\n\n### Recorder\n\nThe last thing we need is a channel that will output `TimingEvent` objects as the load tester runs. This will let us listen to the load testers progress and record stats. We want this channel to be buffered so that we can run somewhat independently of the load test without slowing it down:\n\n``` java\nfinal Channel<TimingEvent<CloseableHttpResponse>> eventCh = Channels.newChannel(10000);\n```\n\nThe `TimingEvent` object contains fields that include the interval time between requests, the duration of the request (how long it took to get a response), whether it was an error or a success, how much \"overage\" time it is experiencing and a few other things (including a field that can be filled in by the request executor).\n\nJBender has a few simple \"recorders\" that make it easy to do basic things like logging events and generating histograms:\n\n* `LoggingRecorder` creates a recorder that takes a `Logger` and outputs each event.\n* `NewHistogramRecorder` records request latencies on a [`org.HdrHistogram.Histogram`](https://github.com/HdrHistogram/HdrHistogram).\n\nYou can combine recorders using the `Recorder.record` function, so you can both log events and manage a\nhistogram using code like this:\n\n```\nfinal Logger LOG = LoggerFactory.getLogger(LoadTest.class);\nfinal Histogram histogram = new Histogram(3600000000L, 3);\nrecord(\"recorder\", eventCh, new HdrHistogramRecorder(histogram), new LoggingRecorder(LOG));\n```\n\nThe histogram takes two arguments: the maximum expected value and the number of precision digits and will\nadjust automatically to record latencies both efficiently and with high-definition buckets.\n\nIt is relatively easy to build recorders, or to just process the events from the channel yourself:\nsee the JBender documentation for more details on what events can be sent, and what data they\ncontain.\n\n### Final Load Tester Program\n\nCreate a directory for the load tester:\n\n``` bash\nmkdir -p src/main/java\n```\n\nThen create a file named `LoadTest.java` in that directory and add these lines to it:\n\n``` java\nimport java.util.concurrent.ExecutionException;\nimport java.io.IOException;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.HdrHistogram.Histogram;\nimport org.apache.http.client.methods.CloseableHttpResponse;\nimport org.apache.http.client.methods.HttpGet;\nimport org.apache.http.nio.reactor.IOReactorException;\nimport co.paralleluniverse.fibers.Fiber;\nimport co.paralleluniverse.fibers.SuspendExecution;\nimport co.paralleluniverse.strands.channels.Channel;\nimport co.paralleluniverse.strands.channels.Channels;\nimport com.pinterest.jbender.JBender;\nimport com.pinterest.jbender.events.Event;\nimport com.pinterest.jbender.events.recording.HdrHistogramRecorder;\nimport com.pinterest.jbender.events.recording.LoggingRecorder;\nimport com.pinterest.jbender.executors.RequestExecutor;\nimport com.pinterest.jbender.executors.http.FiberApacheHttpClientRequestExecutor;\nimport com.pinterest.jbender.intervals.ConstantIntervalGenerator;\nimport com.pinterest.jbender.intervals.IntervalGenerator;\nimport static com.pinterest.jbender.events.recording.Recorder.record;\n\n/**\n * Sample HTTP benchmark against {@url https://github.com/puniverse/comsat-gradle-template}\n */\npublic class LoadTest {\n  public static void main(final String[] args) throws SuspendExecution, InterruptedException, ExecutionException, IOReactorException, IOException {\n    final IntervalGenerator intervalGenerator = new ConstantIntervalGenerator(10000000);\n    try (final FiberApacheHttpClientRequestExecutor requestExecutor =\n            new FiberApacheHttpClientRequestExecutor<>((res) -> {\n              if (res == null) {\n                throw new AssertionError(\"Response is null\");\n              }\n              final int status = res.getStatusLine().getStatusCode();\n              if (status != 200) {\n                throw new AssertionError(\"Status is \" + status);\n              }\n            }, 1000000)) {\n\n      final Channel<HttpGet> requestCh = Channels.newChannel(1000);\n      final Channel<Event<CloseableHttpResponse>> eventCh = Channels.newChannel(1000);\n\n      // Requests generator\n      new Fiber<Void>(\"req-gen\", () -> {\n        // Bench handling 1k reqs\n        for (int i = 0; i < 1000; ++i) {\n          requestCh.send(new HttpGet(\"http://localhost:8080/hello-world\"));\n        }\n\n        requestCh.close();\n      }).start();\n\n      final Histogram histogram = new Histogram(3600000000L, 3);\n\n      // Event recording, both HistHDR and logging\n      record(\"recorder\", eventCh, new HdrHistogramRecorder(histogram), new LoggingRecorder(LOG));\n\n      // Main\n      new Fiber<Void>(\"jbender\", () -> {\n        JBender.loadTestThroughput(intervalGenerator, requestCh, requestExecutor, eventCh);\n      }).start().join();\n\n      histogram.outputPercentileDistribution(System.out, 1000.0);\n    }\n  }\n\n  private static final Logger LOG = LoggerFactory.getLogger(LoadTest.class);\n}\n```\n\n### Run Server and Load Tester\n\nWith the `comsat-gradle-template` server running in one terminal window, run the load tester in\nanother one from the `jbender-http-tutorial` project directory with `gradle wrapper` (only the first time) and then `./gradlew runLoadTest`.\n\nThe output of the load test will be the percentile distribution from the histogram.\n"
  },
  {
    "path": "doc/http/jbender-http-tutorial/build.gradle",
    "content": "// Capsule plugin\nplugins {\n    id \"us.kirchmeier.capsule\" version \"1.0-rc1\"\n}\n\napply plugin: 'java'\n\n// Target JDK8\nsourceCompatibility = 1.8\ntargetCompatibility = 1.8\n\ngroup = 'jbendertut'\nversion = '0.1-SNAPSHOT'\n\n// UTF8 encoding for sources\n[compileJava, compileTestJava]*.options*.encoding = \"UTF-8\"\n\nrepositories {\n    // Enable this if you want to use locally-built artifacts\n    mavenLocal()\n\n    // This allows using published Quasar snapshots\n    maven {\n        url \"https://oss.sonatype.org/content/repositories/snapshots\"\n    }\n\n    mavenCentral()\n}\n\nconfigurations {\n    quasar\n}\n\ndependencies {\n    // Quasar API\n    compile group: \"co.paralleluniverse\", name: \"quasar-core\", version: \"0.6.3-SNAPSHOT\", classifier: \"jdk8\"\n\n    // Comsat HTTP Client\n    compile group: \"co.paralleluniverse\", name: \"comsat-httpclient\", version: \"0.3.0\"\n\n    // HDR histogram\n    compile group: 'org.hdrhistogram', name: 'HdrHistogram', version: \"2.1.4\"\n\n    // JBender API\n    compile group: \"com.pinterest\", name: \"jbender\", version: \"1.0\"\n\n    // Logging\n    compile group: \"org.slf4j\", name: \"slf4j-api\", version: \"1.7.12\"\n    compile group: \"org.slf4j\", name: \"slf4j-simple\", version: \"1.7.12\"\n\n    // Useful to point to the Quasar agent later in JVM flags (and Capsule-building task)\n    quasar group: \"co.paralleluniverse\", name: \"quasar-core\", version: \"0.6.3-SNAPSHOT\", classifier: \"jdk8\"\n}\n\n// Task building an handy self-contained load test capsule\ntask capsule(type: FatCapsule) {\n    applicationClass \"LoadTest\"\n\n    capsuleManifest {\n        // Aggressive optimizations and Quasar agent\n        javaAgents = [configurations.quasar.iterator().next().getName()] // Add \"=vdc\" to the Quasar agent to trace instrumentation\n        jvmArgs = [\"-server\", \"-XX:+TieredCompilation\", \"-XX:+AggressiveOpts\"]\n    }\n}\n\n// Gradle JavaExec load test task\ntask runLoadTest(type: JavaExec) {\n    main = \"LoadTest\"\n\n    classpath = sourceSets.main.runtimeClasspath\n\n    // Aggressive optimizations and Quasar agent\n    jvmArgs = [\"-server\", \"-XX:+TieredCompilation\", \"-XX:+AggressiveOpts\", \"-javaagent:${configurations.quasar.iterator().next()}\"] // Add \"=vdc\" to the Quasar agent to trace instrumentation\n\n    // Enable this to troubleshoot instrumentation issues\n    // systemProperties = [\"co.paralleluniverse.fibers.verifyInstrumentation\" : \"true\"]\n}\n"
  },
  {
    "path": "doc/http/jbender-http-tutorial/gradle/wrapper/gradle-wrapper.properties",
    "content": "#Tue May 12 13:10:51 PDT 2015\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-2.4-bin.zip\n"
  },
  {
    "path": "doc/http/jbender-http-tutorial/gradlew",
    "content": "#!/usr/bin/env bash\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS=\"\"\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn ( ) {\n    echo \"$*\"\n}\n\ndie ( ) {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\nesac\n\n# For Cygwin, ensure paths are in UNIX format before anything is touched.\nif $cygwin ; then\n    [ -n \"$JAVA_HOME\" ] && JAVA_HOME=`cygpath --unix \"$JAVA_HOME\"`\nfi\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >&-\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >&-\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=$((i+1))\n    done\n    case $i in\n        (0) set -- ;;\n        (1) set -- \"$args0\" ;;\n        (2) set -- \"$args0\" \"$args1\" ;;\n        (3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        (4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        (5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        (6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        (7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        (8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        (9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules\nfunction splitJvmOpts() {\n    JVM_OPTS=(\"$@\")\n}\neval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\nJVM_OPTS[${#JVM_OPTS[*]}]=\"-Dorg.gradle.appname=$APP_BASE_NAME\"\n\nexec \"$JAVACMD\" \"${JVM_OPTS[@]}\" -classpath \"$CLASSPATH\" org.gradle.wrapper.GradleWrapperMain \"$@\"\n"
  },
  {
    "path": "doc/http/jbender-http-tutorial/gradlew.bat",
    "content": "@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif \"%ERRORLEVEL%\" == \"0\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:init\r\n@rem Get command-line arguments, handling Windowz variants\r\n\r\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\r\nif \"%@eval[2+2]\" == \"4\" goto 4NT_args\r\n\r\n:win9xME_args\r\n@rem Slurp the command line arguments.\r\nset CMD_LINE_ARGS=\r\nset _SKIP=2\r\n\r\n:win9xME_args_slurp\r\nif \"x%~1\" == \"x\" goto execute\r\n\r\nset CMD_LINE_ARGS=%*\r\ngoto execute\r\n\r\n:4NT_args\r\n@rem Get arguments from the 4NT Shell from JP Software\r\nset CMD_LINE_ARGS=%$\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\r\nexit /b 1\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "doc/http/jbender-http-tutorial/src/main/java/LoadTest.java",
    "content": "import java.util.concurrent.ExecutionException;\nimport java.io.IOException;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.HdrHistogram.Histogram;\nimport org.apache.http.client.methods.CloseableHttpResponse;\nimport org.apache.http.client.methods.HttpGet;\nimport org.apache.http.nio.reactor.IOReactorException;\nimport co.paralleluniverse.fibers.Fiber;\nimport co.paralleluniverse.fibers.SuspendExecution;\nimport co.paralleluniverse.strands.channels.Channel;\nimport co.paralleluniverse.strands.channels.Channels;\nimport com.pinterest.jbender.JBender;\nimport com.pinterest.jbender.events.TimingEvent;\nimport com.pinterest.jbender.events.recording.HdrHistogramRecorder;\nimport com.pinterest.jbender.events.recording.LoggingRecorder;\nimport com.pinterest.jbender.executors.RequestExecutor;\nimport com.pinterest.jbender.executors.http.FiberApacheHttpClientRequestExecutor;\nimport com.pinterest.jbender.intervals.ConstantIntervalGenerator;\nimport com.pinterest.jbender.intervals.IntervalGenerator;\nimport static com.pinterest.jbender.events.recording.Recorder.record;\n\n/**\n * Sample HTTP benchmark against {@url https://github.com/puniverse/comsat-gradle-template}\n */\npublic class LoadTest {\n  public static void main(final String[] args) throws SuspendExecution, InterruptedException, ExecutionException, IOReactorException, IOException {\n    final IntervalGenerator intervalGenerator = new ConstantIntervalGenerator(10000000);\n    try (final FiberApacheHttpClientRequestExecutor requestExecutor =\n            new FiberApacheHttpClientRequestExecutor<>((res) -> {\n              if (res == null) {\n                throw new AssertionError(\"Response is null\");\n              }\n              final int status = res.getStatusLine().getStatusCode();\n              if (status != 200) {\n                throw new AssertionError(\"Status is \" + status);\n              }\n            }, 1000000)) {\n\n      final Channel<HttpGet> requestCh = Channels.newChannel(1000);\n      final Channel<TimingEvent<CloseableHttpResponse>> eventCh = Channels.newChannel(1000);\n\n      // Requests generator\n      new Fiber<Void>(\"req-gen\", () -> {\n        // Bench handling 1k reqs\n        for (int i = 0; i < 1000; ++i) {\n          requestCh.send(new HttpGet(\"http://localhost:8080/hello-world\"));\n        }\n\n        requestCh.close();\n      }).start();\n\n      final Histogram histogram = new Histogram(3600000000L, 3);\n\n      // Event recording, both HistHDR and logging\n      record(eventCh, new HdrHistogramRecorder(histogram, 1000000), new LoggingRecorder(LOG));\n\n      // Main\n      new Fiber<Void>(\"jbender\", () -> {\n        JBender.loadTestThroughput(intervalGenerator, 0, requestCh, requestExecutor, eventCh);\n      }).start().join();\n\n      histogram.outputPercentileDistribution(System.out, 1000.0);\n    }\n  }\n\n  private static final Logger LOG = LoggerFactory.getLogger(LoadTest.class);\n}\n"
  },
  {
    "path": "doc/thrift/TUTORIAL.md",
    "content": "JBender Thrift Tutorial\n=======================\n\nThis tutorial walks through the steps to create a simple \"Echo\" Thrift service with a load tester. The complete\nproject can be found at [jbender-echo](https://github.com/cgordon/jbender-echo) TODO: open it up or include it\nhere.\n\n## Getting Started\n\nYou will need a copy of Thrift installed on your machine, which will allow you to run the `thrift`\ncommand. You can follow the \"Getting Started\" instructions on the [Apache Thrift](https://thrift.apache.org/) \npage to download and install it. The easiest way to get it on Mac OS X is probably to install\n[HomeBrew](http://brew.sh/) and then `brew install gradle` though.\n\nJBender uses [Gradle](http://gradle.org) as a build tool and we're going to use it for our sample load\ntester as well, so make sure you have it installed. The easiest way to get it on Mac OS X is still\n`brew install gradle`, while on Linux there's [LinuxBrew](https://github.com/Homebrew/linuxbrew).\nYour specific Linux distribution could offer native Gradle packages but they tend to lag behind the\nmost recent version, so it's probably better to brew anyway.\n\n### Creating the load test Gradle project\n\nIn your usual sources work root create a `jbender-thrift-tutorial` directory and the following `build.gradle` file in it:\n\n``` groovy\n// Gradle Thrift plugin\nbuildscript {\n    repositories {\n        mavenCentral()\n    }\n\n    dependencies {\n        classpath \"co.tomlee.gradle.plugins:gradle-thrift-plugin:0.0.4\"\n    }\n}\n\n// Capsule plugin\nplugins {\n    id \"us.kirchmeier.capsule\" version \"1.0-rc1\"\n}\n\napply plugin: 'java'\napply plugin: 'thrift'\n\n// Target JDK8\nsourceCompatibility = 1.8\ntargetCompatibility = 1.8\n\ngroup = 'jbendertut'\nversion = '0.1-SNAPSHOT'\n\n// UTF8 encoding for sources\n[compileJava, compileTestJava]*.options*.encoding = \"UTF-8\"\n\nrepositories {\n    // Enable this if you want to use locally-built artifacts\n    mavenLocal()\n    \n    mavenCentral()\n}\n\nconfigurations {\n    quasar\n}\n\ndependencies {\n    // Thrift API\n    compile group: \"org.apache.thrift\", name: \"libthrift\", version: \"0.9.1\"\n\n    // Quasar-Thrift server\n    compile group: \"com.pinterest\", name: \"quasar-thrift\", version: \"0.1-SNAPSHOT\"\n\n    // Quasar API\n    compile group: \"co.paralleluniverse\", name: \"quasar-core\", version: \"0.7.3\", classifier: \"jdk8\"\n\n    // JBender API\n    compile group: \"com.pinterest\", name: \"jbender\", version: \"1.0\"\n\n    // Logging\n    compile group: \"org.slf4j\", name: \"slf4j-api\", version: \"1.7.12\"\n    compile group: \"org.slf4j\", name: \"slf4j-simple\", version: \"1.7.12\"\n\n    // Useful to point to the Quasar agent later in JVM flags (and Capsule-building task)\n    quasar group: \"co.paralleluniverse\", name: \"quasar-core\", version: \"0.7.3\", classifier: \"jdk8\"\n}\n\n// Thrift generators\ngenerateThriftSource {\n    generators { java {} }\n}\n\n// Automatically find Quasar suspendables in Thrift-generated code\nclasses {\n    doFirst {\n        ant.taskdef(name: 'scanSuspendables',\n                classname: 'co.paralleluniverse.fibers.instrument.SuspendablesScanner',\n                classpath: \"build/classes/main:build/resources/main:${configurations.runtime.asPath}\")\n        ant.scanSuspendables(\n                auto: true,\n                suspendablesFile: \"$sourceSets.main.output.resourcesDir/META-INF/suspendables\",\n                supersFile: \"$sourceSets.main.output.resourcesDir/META-INF/suspendable-supers\",\n                append: true) {\n            fileset(dir: sourceSets.main.output.classesDir)\n        }\n    }\n}\n\n// Task building an handy self-contained server capsule\ntask serverCapsule(type: FatCapsule) {\n    applicationClass \"com.pinterest.echo.jbender.server.Main\"\n\n    capsuleManifest {\n        javaAgents = [configurations.quasar.iterator().next().getName()]\n        // Aggressive optimizations\n        jvmArgs = [\"-server\", \"-XX:+TieredCompilation\", \"-XX:+AggressiveOpts\"]\n    }\n}\n\n// Task building an handy self-contained load test capsule\ntask capsule(type: FatCapsule) {\n    applicationClass \"com.pinterest.echo.jbender.Main\"\n\n    capsuleManifest {\n        javaAgents = [configurations.quasar.iterator().next().getName()]\n        // Aggressive optimizations\n        jvmArgs = [\"-server\", \"-XX:+TieredCompilation\", \"-XX:+AggressiveOpts\"]\n    }\n}\n\n// Gradle JavaExec load test task\ntask runLoadTest(type: JavaExec) {\n    main = \"com.pinterest.echo.jbender.Main\"\n\n    classpath = sourceSets.main.runtimeClasspath\n\n    // Aggressive optimizations and Quasar agent\n    jvmArgs = [\"-server\", \"-XX:+TieredCompilation\", \"-XX:+AggressiveOpts\", \"-javaagent:${configurations.quasar.iterator().next()}\"] // Add \"=vdc\" to the Quasar agent to trace instrumentation\n\n    // Enable this to troubleshoot instrumentation issues\n    // systemProperties = [\"co.paralleluniverse.fibers.verifyInstrumentation\" : \"true\"]\n}\n\n// Gradle JavaExec server task\ntask runServer(type: JavaExec) {\n    main = \"com.pinterest.echo.jbender.server.Main\"\n\n    classpath = sourceSets.main.runtimeClasspath\n\n    // Aggressive optimizations and Quasar agent\n    jvmArgs = [\"-server\", \"-XX:+TieredCompilation\", \"-XX:+AggressiveOpts\", \"-javaagent:${configurations.quasar.iterator().next()}\"] // Add \"=vdc\" to the Quasar agent to trace instrumentation\n\n    // Enable this to troubleshoot instrumentation issues\n    // systemProperties = [\"co.paralleluniverse.fibers.verifyInstrumentation\" : \"true\"]\n}\n```\n\n\n## Writing the Thrift Server and Client\n\nThis section will walk through the creation of a Thrift client and server, which we will use to\ntest JBender in the following section.\n\n### Thrift Service Definition and Code Generation\n\nNow create a file named `src/main/thrift/echo.thrift` and add these lines to it using your\ntext editor:\n\n``` thrift\nnamespace java com.pinterest.echo.thrift\n\nstruct EchoRequest {\n    1: optional string message;\n}\n\nstruct EchoResponse {\n    2: optional string message;\n}\n\nservice EchoService {\n    EchoResponse echo(1: EchoRequest request);\n}\n```\n\nThis defines a Thrift service with one API endpoint named `echo` that takes a `EchoRequest` and\nreturns a `EchoResponse`.\n\n### Thrift Service Implementation\n\nNow we will create a simple service definition that just echoes the request string to the response.\nFirst, create a new directory:\n\n```\nmkdir -p src/main/java/echo/server\n```\n\nThen create a file named `Main.java` in that directory and add these lines to it:\n\n``` java\npackage echo.server;\n\nimport co.paralleluniverse.fibers.Suspendable;\nimport com.pinterest.quasar.thrift.TFiberServer;\nimport com.pinterest.quasar.thrift.TFiberServerSocket;\nimport echo.thrift.EchoRequest;\nimport echo.thrift.EchoResponse;\nimport echo.thrift.EchoService;\nimport org.apache.thrift.TException;\nimport org.apache.thrift.protocol.TBinaryProtocol;\nimport org.apache.thrift.transport.TFastFramedTransport;\nimport java.net.InetSocketAddress;\n\npublic class Main {\n    static final class EchoServiceImpl implements EchoService.Iface {\n        @Override\n        @Suspendable\n        public EchoResponse echo(EchoRequest request) throws TException {\n            return new EchoResponse().setMessage(request.getMessage());\n        }\n    }\n\n    @Suspendable\n    public static void main(String[] args) throws Exception {\n        EchoService.Processor<EchoService.Iface> processor =\n            new EchoService.Processor<EchoService.Iface>(new EchoServiceImpl());\n        TFiberServerSocket trans = new TFiberServerSocket(new InetSocketAddress(9999));\n        TFiberServer.Args targs = new TFiberServer.Args(trans, processor)\n            .protocolFactory(new TBinaryProtocol.Factory())\n            .transportFactory(new TFastFramedTransport.Factory());\n        TFiberServer server = new TFiberServer(targs);\n        server.serve();\n        server.join();\n    }\n}\n```\n\n\n## Load Testing\n\nLet's now write a simple load tester with JBender. The next few sections walk through the various parts of the load tester. If you are in a hurry skip to the section \"Final Load Tester Program\" and just follow the instructions from there.\n\n### Intervals\n\nThe first thing we need is a function to generate intervals (in nanoseconds) between executing\nrequests. The JBender library comes with some predefined intervals: a uniform distribution\n(always wait the same amount of time between each request) and an exponential distribution. In this\ncase we will use the exponential distribution, which means our server will experience load as\ngenerated by a [Poisson process](http://en.wikipedia.org/wiki/Poisson_process), which is fairly\ntypical of server workloads on the Internet (with the usual caveats that every service is a special\nsnowflake, etc, etc). We get the interval function with this code:\n\n``` java\nfinal IntervalGenerator intervalGenerator = new ConstantIntervalGenerator(qps);\n```\n\nWhere `qps` is our desired throughput measured in queries per second. It is also the reciprocal of\nthe mean value of the exponential distribution used to generate the request arrival times (see the\nwikipedia article above). In practice this means you will see an average QPS that fluctuates around\nthe target QPS (with less fluctuation as you increase the time interval over which you are\naveraging).\n\n### Request Generator\n\nThe second thing we need is a channel of requests to send to the HTTP server. When an interval has\nbeen generated and JBender is ready to send the request, it pulls the next request from this channel\nand spawns a Quasar _fiber_ (lightweight thread) to send the request to the server. This code creates\nand starts a simple synthetic Apache HTTP Client's `HttpGet` request generator to the \"Hello World\"\nserver endpoint:\n\n``` java\nnew Fiber<Void>(\"message-producer\", () -> {\n  // Bench handling 10k reqs\n  for (int i = 0; i < 10000; ++i) {\n    requestCh.send(new HttpGet(\"http://localhost:8080/hello-world\"));\n  }\n\n  requestCh.close();\n  return null;\n}).start();\n```\n\n### Request Executor\n\nThe next thing we need is a request executor, which takes the requests generated above and sends\nthem to the service. We will just use JBender's pre-built one and add a response validator:\n\n```\nfinal RequestExecutor<HttpGet, CloseableHttpResponse> requestExecutor =\n  new FiberApacheHttpClientRequestExecutor<>((res) -> {\n    if (res == null) {\n      throw new AssertionError(\"Response is null\");\n    }\n    final int status = res.getStatusLine().getStatusCode();\n    if (status != 200) {\n      throw new AssertionError(\"Status \" + status + \" is not 200\");\n    }\n  }, 1000000);\n```\n\nThis validates that the response has actually been produced ans has a HTTP 200 status code.\n\n### Recording Results\n\nThe last thing we need is a channel that will output events as the load tester runs. This will let\nus listen to the load testers progress and record stats. We want this channel to be buffered so that\nwe can run somewhat independently of the load test without slowing it down:\n\n``` java\nfinal Channel<Event<CloseableHttpResponse>> eventCh = Channels.newChannel(10000);\n```\n\nThe `JBender.loadTestThroughput` function will send there events for things like how long it waits\nbetween requests, how much overage it is currently experiencing, and when requests start and end,\nhow long they took and whether or not they had errors. That raw event stream makes it possible to\nanalyze the results of a load test. JBender has a couple simple \"recorders\" that provide basic\nfunctionality for result analysis:\n\n* `LoggingRecorder` creates a recorder that takes a `Logger` and outputs each event.\n* `NewHistogramRecorder` records request latencies on a [`org.HdrHistogram.Histogram`](https://github.com/HdrHistogram/HdrHistogram).\n\nYou can combine recorders using the `Recorder.record` function, so you can both log events and manage a\nhistogram using code like this:\n\n```\nfinal Logger LOG = LoggerFactory.getLogger(LoadTest.class);\nfinal Histogram histogram = new Histogram(3600000000L, 3);\nrecord(\"recorder\", eventCh, new HdrHistogramRecorder(histogram), new LoggingRecorder(LOG));\n```\n\nThe histogram takes two arguments: the maximum expected value and the number of precision digits and will\nadjust automatically to record latencies both efficiently and with high-definition buckets.\n\nIt is relatively easy to build recorders, or to just process the events from the channel yourself:\nsee the JBender documentation for more details on what events can be sent, and what data they\ncontain.\n\n### Final Load Tester Program\n\nThen create a file named `src/main/java/echo/jbender/Main.java`:\n\n``` java\npackage echo.jbender;\n\nimport co.paralleluniverse.fibers.SuspendExecution;\nimport co.paralleluniverse.strands.channels.Channel;\nimport co.paralleluniverse.strands.channels.Channels;\nimport co.paralleluniverse.fibers.Fiber;\nimport echo.thrift.EchoService;\nimport echo.thrift.EchoRequest;\nimport echo.thrift.EchoResponse;\nimport com.pinterest.jbender.JBender;\nimport com.pinterest.jbender.events.TimingEvent;\nimport com.pinterest.jbender.events.recording.HdrHistogramRecorder;\nimport com.pinterest.jbender.events.recording.LoggingRecorder;\nimport com.pinterest.jbender.executors.RequestExecutor;\nimport com.pinterest.jbender.intervals.ConstantIntervalGenerator;\nimport com.pinterest.jbender.intervals.IntervalGenerator;\nimport com.pinterest.quasar.thrift.TFiberSocket;\nimport org.apache.thrift.protocol.TBinaryProtocol;\nimport org.apache.thrift.protocol.TProtocol;\nimport org.apache.thrift.transport.TFastFramedTransport;\nimport org.HdrHistogram.Histogram;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport java.net.InetSocketAddress;\nimport java.util.concurrent.TimeUnit;\n\nimport static com.pinterest.jbender.events.recording.Recorder.record;\n\npublic class Main {\n  static final class EchoRequestExecutor implements RequestExecutor<EchoRequest, EchoResponse> {\n    @Override\n    public EchoResponse execute(long l, EchoRequest echoRequest) throws SuspendExecution, InterruptedException {\n      try {\n        TProtocol proto = new TBinaryProtocol(new TFastFramedTransport(TFiberSocket.open(new InetSocketAddress(\"localhost\", 9999))));\n        EchoService.Client client = new EchoService.Client(proto);\n        return client.echo(echoRequest);\n      } catch (Exception ex) {\n        LOG.error(\"failed to echo\", ex);\n        throw new RuntimeException(ex);\n      }\n    }\n  }\n\n  public static void main(String[] args) throws SuspendExecution, InterruptedException {\n    final IntervalGenerator intervalGen = new ConstantIntervalGenerator(10000000);\n    final RequestExecutor<EchoRequest, EchoResponse> requestExector = new EchoRequestExecutor();\n\n    final Channel<EchoRequest> requestCh = Channels.newChannel(-1);\n    final Channel<TimingEvent<EchoResponse>> eventCh = Channels.newChannel(-1);\n\n    // Requests generator\n    new Fiber<Void>(\"req-gen\", () -> {\n      for (int i=0; i < 1000; ++i) {\n        final EchoRequest req = new EchoRequest();\n        req.setMessage(\"foo\");\n        requestCh.send(req);\n      }\n\n      requestCh.close();\n    }).start();\n\n    final Histogram histogram = new Histogram(3600000000L, 3);\n    // Event recording, both HistHDR and logging\n    record(eventCh, new HdrHistogramRecorder(histogram, 1000000), new LoggingRecorder(LOG));\n\n    JBender.loadTestThroughput(intervalGen, 0, requestCh, requestExector, eventCh);\n\n    histogram.outputPercentileDistribution(System.out, 1000.0);\n  }\n\n  private static final Logger LOG = LoggerFactory.getLogger(Main.class);\n}\n```\n\n### Run Server and Load Tester\n\nThe first time you use these instructions, run `gradle wrapper` to create the gradle wrapper.\n\nWith `./gradlew runServer` running in one terminal window, run the load tester in\nanother one with `./gradlew runLoadTest`.\n\nThe output of the load test will be the percentile distribution from the histogram.\n"
  },
  {
    "path": "doc/thrift/jbender-thrift-tutorial/build.gradle",
    "content": "// Gradle Thrift plugin\nbuildscript {\n    repositories {\n        mavenCentral()\n    }\n\n    dependencies {\n        classpath \"co.tomlee.gradle.plugins:gradle-thrift-plugin:0.0.4\"\n    }\n}\n\n// Capsule plugin\nplugins {\n    id \"us.kirchmeier.capsule\" version \"1.0-rc1\"\n}\n\napply plugin: 'java'\napply plugin: 'thrift'\n\n// Target JDK8\nsourceCompatibility = 1.8\ntargetCompatibility = 1.8\n\ngroup = 'jbendertut'\nversion = '0.1-SNAPSHOT'\n\n// UTF8 encoding for sources\n[compileJava, compileTestJava]*.options*.encoding = \"UTF-8\"\n\nrepositories {\n    // Enable this if you want to use locally-built artifacts\n    mavenLocal()\n\n    mavenCentral()\n}\n\nconfigurations {\n    quasar\n}\n\ndependencies {\n    // Thrift API\n    compile group: \"org.apache.thrift\", name: \"libthrift\", version: \"0.9.1\"\n\n    // Quasar-Thrift server\n    compile group: \"com.pinterest\", name: \"quasar-thrift\", version: \"0.3\"\n\n    // Quasar API\n    compile group: \"co.paralleluniverse\", name: \"quasar-core\", version: \"0.7.3\", classifier: \"jdk8\"\n\n    // JBender API\n    compile group: \"com.pinterest\", name: \"jbender\", version: \"1.0\"\n\n    // Logging\n    compile group: \"org.slf4j\", name: \"slf4j-api\", version: \"1.7.12\"\n    compile group: \"org.slf4j\", name: \"slf4j-simple\", version: \"1.7.12\"\n\n    // Useful to point to the Quasar agent later in JVM flags (and Capsule-building task)\n    quasar group: \"co.paralleluniverse\", name: \"quasar-core\", version: \"0.7.3\", classifier: \"jdk8\"\n}\n\n// Thrift generators\ngenerateThriftSource {\n    generators { java {} }\n}\n\n// Automatically find Quasar suspendables in Thrift-generated code\nclasses {\n    doFirst {\n        ant.taskdef(name: 'scanSuspendables',\n                classname: 'co.paralleluniverse.fibers.instrument.SuspendablesScanner',\n                classpath: \"build/classes/main:build/resources/main:${configurations.runtime.asPath}\")\n        ant.scanSuspendables(\n                auto: true,\n                suspendablesFile: \"$sourceSets.main.output.resourcesDir/META-INF/suspendables\",\n                supersFile: \"$sourceSets.main.output.resourcesDir/META-INF/suspendable-supers\",\n                append: true) {\n            fileset(dir: sourceSets.main.output.classesDir)\n        }\n    }\n}\n\n// Task building an handy self-contained server capsule\ntask serverCapsule(type: FatCapsule) {\n    applicationClass \"com.pinterest.echo.jbender.server.Main\"\n\n    capsuleManifest {\n        javaAgents = [configurations.quasar.iterator().next().getName()]\n        // Aggressive optimizations\n        jvmArgs = [\"-server\", \"-XX:+TieredCompilation\", \"-XX:+AggressiveOpts\"]\n    }\n}\n\n// Task building an handy self-contained load test capsule\ntask capsule(type: FatCapsule) {\n    applicationClass \"com.pinterest.echo.jbender.Main\"\n\n    capsuleManifest {\n        javaAgents = [configurations.quasar.iterator().next().getName()]\n        // Aggressive optimizations\n        jvmArgs = [\"-server\", \"-XX:+TieredCompilation\", \"-XX:+AggressiveOpts\"]\n    }\n}\n\n// Gradle JavaExec load test task\ntask runLoadTest(type: JavaExec) {\n    main = \"echo.jbender.Main\"\n\n    classpath = sourceSets.main.runtimeClasspath\n\n    // Aggressive optimizations and Quasar agent\n    jvmArgs = [\"-server\", \"-XX:+TieredCompilation\", \"-XX:+AggressiveOpts\", \"-javaagent:${configurations.quasar.iterator().next()}\"] // Add \"=vdc\" to the Quasar agent to trace instrumentation\n\n    // Enable this to troubleshoot instrumentation issues\n    // systemProperties = [\"co.paralleluniverse.fibers.verifyInstrumentation\" : \"true\"]\n}\n\n// Gradle JavaExec server task\ntask runServer(type: JavaExec) {\n    main = \"echo.server.Main\"\n\n    classpath = sourceSets.main.runtimeClasspath\n\n    // Aggressive optimizations and Quasar agent\n    jvmArgs = [\"-server\", \"-XX:+TieredCompilation\", \"-XX:+AggressiveOpts\", \"-javaagent:${configurations.quasar.iterator().next()}\"] // Add \"=vdc\" to the Quasar agent to trace instrumentation\n\n    // Enable this to troubleshoot instrumentation issues\n    // systemProperties = [\"co.paralleluniverse.fibers.verifyInstrumentation\" : \"true\"]\n}\n"
  },
  {
    "path": "doc/thrift/jbender-thrift-tutorial/gradle/wrapper/gradle-wrapper.properties",
    "content": "#Mon Nov 02 11:45:44 PST 2015\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-2.6-bin.zip\n"
  },
  {
    "path": "doc/thrift/jbender-thrift-tutorial/gradlew",
    "content": "#!/usr/bin/env bash\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS=\"\"\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn ( ) {\n    echo \"$*\"\n}\n\ndie ( ) {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\nesac\n\n# For Cygwin, ensure paths are in UNIX format before anything is touched.\nif $cygwin ; then\n    [ -n \"$JAVA_HOME\" ] && JAVA_HOME=`cygpath --unix \"$JAVA_HOME\"`\nfi\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >&-\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >&-\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=$((i+1))\n    done\n    case $i in\n        (0) set -- ;;\n        (1) set -- \"$args0\" ;;\n        (2) set -- \"$args0\" \"$args1\" ;;\n        (3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        (4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        (5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        (6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        (7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        (8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        (9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules\nfunction splitJvmOpts() {\n    JVM_OPTS=(\"$@\")\n}\neval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\nJVM_OPTS[${#JVM_OPTS[*]}]=\"-Dorg.gradle.appname=$APP_BASE_NAME\"\n\nexec \"$JAVACMD\" \"${JVM_OPTS[@]}\" -classpath \"$CLASSPATH\" org.gradle.wrapper.GradleWrapperMain \"$@\"\n"
  },
  {
    "path": "doc/thrift/jbender-thrift-tutorial/gradlew.bat",
    "content": "@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif \"%ERRORLEVEL%\" == \"0\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:init\r\n@rem Get command-line arguments, handling Windowz variants\r\n\r\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\r\nif \"%@eval[2+2]\" == \"4\" goto 4NT_args\r\n\r\n:win9xME_args\r\n@rem Slurp the command line arguments.\r\nset CMD_LINE_ARGS=\r\nset _SKIP=2\r\n\r\n:win9xME_args_slurp\r\nif \"x%~1\" == \"x\" goto execute\r\n\r\nset CMD_LINE_ARGS=%*\r\ngoto execute\r\n\r\n:4NT_args\r\n@rem Get arguments from the 4NT Shell from JP Software\r\nset CMD_LINE_ARGS=%$\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\r\nexit /b 1\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "doc/thrift/jbender-thrift-tutorial/src/main/java/echo/jbender/Main.java",
    "content": "package echo.jbender;\n\nimport co.paralleluniverse.fibers.SuspendExecution;\nimport co.paralleluniverse.strands.channels.Channel;\nimport co.paralleluniverse.strands.channels.Channels;\nimport co.paralleluniverse.fibers.Fiber;\nimport echo.thrift.EchoService;\nimport echo.thrift.EchoRequest;\nimport echo.thrift.EchoResponse;\nimport com.pinterest.jbender.JBender;\nimport com.pinterest.jbender.events.TimingEvent;\nimport com.pinterest.jbender.events.recording.HdrHistogramRecorder;\nimport com.pinterest.jbender.events.recording.LoggingRecorder;\nimport com.pinterest.jbender.executors.RequestExecutor;\nimport com.pinterest.jbender.intervals.ConstantIntervalGenerator;\nimport com.pinterest.jbender.intervals.IntervalGenerator;\nimport com.pinterest.quasar.thrift.TFiberSocket;\nimport org.apache.thrift.protocol.TBinaryProtocol;\nimport org.apache.thrift.protocol.TProtocol;\nimport org.apache.thrift.transport.TFastFramedTransport;\nimport org.HdrHistogram.Histogram;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport java.net.InetSocketAddress;\nimport java.util.concurrent.TimeUnit;\n\nimport static com.pinterest.jbender.events.recording.Recorder.record;\n\npublic class Main {\n  static final class EchoRequestExecutor implements RequestExecutor<EchoRequest, EchoResponse> {\n    @Override\n    public EchoResponse execute(long l, EchoRequest echoRequest) throws SuspendExecution, InterruptedException {\n      try {\n        TProtocol proto = new TBinaryProtocol(new TFastFramedTransport(TFiberSocket.open(new InetSocketAddress(\"localhost\", 9999))));\n        EchoService.Client client = new EchoService.Client(proto);\n        return client.echo(echoRequest);\n      } catch (Exception ex) {\n        LOG.error(\"failed to echo\", ex);\n        throw new RuntimeException(ex);\n      }\n    }\n  }\n\n  public static void main(String[] args) throws SuspendExecution, InterruptedException {\n    final IntervalGenerator intervalGen = new ConstantIntervalGenerator(10000000);\n    final RequestExecutor<EchoRequest, EchoResponse> requestExector = new EchoRequestExecutor();\n\n    final Channel<EchoRequest> requestCh = Channels.newChannel(-1);\n    final Channel<TimingEvent<EchoResponse>> eventCh = Channels.newChannel(-1);\n\n    // Requests generator\n    new Fiber<Void>(\"req-gen\", () -> {\n      for (int i=0; i < 1000; ++i) {\n        final EchoRequest req = new EchoRequest();\n        req.setMessage(\"foo\");\n        requestCh.send(req);\n      }\n\n      requestCh.close();\n    }).start();\n\n    final Histogram histogram = new Histogram(3600000000L, 3);\n    // Event recording, both HistHDR and logging\n    record(eventCh, new HdrHistogramRecorder(histogram, 1000000), new LoggingRecorder(LOG));\n\n    JBender.loadTestThroughput(intervalGen, 0, requestCh, requestExector, eventCh);\n\n    histogram.outputPercentileDistribution(System.out, 1000.0);\n  }\n\n  private static final Logger LOG = LoggerFactory.getLogger(Main.class);\n}\n"
  },
  {
    "path": "doc/thrift/jbender-thrift-tutorial/src/main/java/echo/server/Main.java",
    "content": "package echo.server;\n\nimport co.paralleluniverse.fibers.Suspendable;\nimport com.pinterest.quasar.thrift.TFiberServer;\nimport com.pinterest.quasar.thrift.TFiberServerSocket;\nimport echo.thrift.EchoRequest;\nimport echo.thrift.EchoResponse;\nimport echo.thrift.EchoService;\nimport org.apache.thrift.TException;\nimport org.apache.thrift.protocol.TBinaryProtocol;\nimport org.apache.thrift.transport.TFastFramedTransport;\nimport java.net.InetSocketAddress;\n\npublic class Main {\n    static final class EchoServiceImpl implements EchoService.Iface {\n        @Override\n        @Suspendable\n        public EchoResponse echo(EchoRequest request) throws TException {\n            return new EchoResponse().setMessage(request.getMessage());\n        }\n    }\n\n    @Suspendable\n    public static void main(String[] args) throws Exception {\n        EchoService.Processor<EchoService.Iface> processor =\n            new EchoService.Processor<EchoService.Iface>(new EchoServiceImpl());\n        TFiberServerSocket trans = new TFiberServerSocket(new InetSocketAddress(9999));\n        TFiberServer.Args targs = new TFiberServer.Args(trans, processor)\n            .protocolFactory(new TBinaryProtocol.Factory())\n            .transportFactory(new TFastFramedTransport.Factory());\n        TFiberServer server = new TFiberServer(targs);\n        server.serve();\n        server.join();\n    }\n}\n"
  },
  {
    "path": "doc/thrift/jbender-thrift-tutorial/src/main/thrift/echo.thrift",
    "content": "namespace java echo.thrift\n\nstruct EchoRequest {\n    1: optional string message;\n}\n\nstruct EchoResponse {\n    2: optional string message;\n}\n\nservice EchoService {\n    EchoResponse echo(1: EchoRequest request);\n}\n"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "#Tue Aug 02 17:39:27 PDT 2016\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-2.14.1-bin.zip\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStorePath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\n"
  },
  {
    "path": "gradlew",
    "content": "#!/usr/bin/env bash\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS=\"\"\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn ( ) {\n    echo \"$*\"\n}\n\ndie ( ) {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\n  NONSTOP* )\n    nonstop=true\n    ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" -a \"$nonstop\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=$((i+1))\n    done\n    case $i in\n        (0) set -- ;;\n        (1) set -- \"$args0\" ;;\n        (2) set -- \"$args0\" \"$args1\" ;;\n        (3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        (4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        (5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        (6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        (7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        (8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        (9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules\nfunction splitJvmOpts() {\n    JVM_OPTS=(\"$@\")\n}\neval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\nJVM_OPTS[${#JVM_OPTS[*]}]=\"-Dorg.gradle.appname=$APP_BASE_NAME\"\n\nexec \"$JAVACMD\" \"${JVM_OPTS[@]}\" -classpath \"$CLASSPATH\" org.gradle.wrapper.GradleWrapperMain \"$@\"\n"
  },
  {
    "path": "gradlew.bat",
    "content": "@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif \"%ERRORLEVEL%\" == \"0\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:init\r\n@rem Get command-line arguments, handling Windows variants\r\n\r\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\r\nif \"%@eval[2+2]\" == \"4\" goto 4NT_args\r\n\r\n:win9xME_args\r\n@rem Slurp the command line arguments.\r\nset CMD_LINE_ARGS=\r\nset _SKIP=2\r\n\r\n:win9xME_args_slurp\r\nif \"x%~1\" == \"x\" goto execute\r\n\r\nset CMD_LINE_ARGS=%*\r\ngoto execute\r\n\r\n:4NT_args\r\n@rem Get arguments from the 4NT Shell from JP Software\r\nset CMD_LINE_ARGS=%$\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\r\nexit /b 1\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "settings.gradle",
    "content": "rootProject.name = 'jbender'\n\n"
  },
  {
    "path": "src/jmh/java/com/pinterest/jbender/JBenderBenchmark.java",
    "content": "/*\nCopyright 2014 Pinterest.com\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n    http://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n */\npackage com.pinterest.jbender;\n\nimport co.paralleluniverse.fibers.Fiber;\nimport co.paralleluniverse.fibers.SuspendExecution;\nimport co.paralleluniverse.strands.channels.Channel;\nimport co.paralleluniverse.strands.channels.Channels;\nimport com.pinterest.jbender.events.TimingEvent;\nimport com.pinterest.jbender.events.recording.HdrHistogramRecorder;\nimport com.pinterest.jbender.executors.NoopRequestExecutor;\nimport com.pinterest.jbender.executors.RequestExecutor;\nimport com.pinterest.jbender.intervals.ConstantIntervalGenerator;\nimport com.pinterest.jbender.intervals.IntervalGenerator;\nimport org.HdrHistogram.Histogram;\nimport org.openjdk.jmh.annotations.Benchmark;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.util.concurrent.ExecutionException;\n\nimport static com.pinterest.jbender.events.recording.Recorder.record;\n\npublic class JBenderBenchmark {\n\n  // @Benchmark\n  public Histogram loadtestThroughput() throws SuspendExecution, InterruptedException, ExecutionException {\n    final IntervalGenerator intervalGenerator = new ConstantIntervalGenerator(1000);\n    final RequestExecutor<String, Void> requestExecutor = new NoopRequestExecutor<>();\n\n    final Channel<String> requestCh = Channels.newChannel(10000);\n    final Channel<TimingEvent<Void>> eventCh = Channels.newChannel(10000);\n\n    // Requests generator\n    new Fiber<Void>(\"req-gen\", () -> {\n      // Bench handling 10k reqs\n      for (int i = 0; i < 10000; ++i) {\n        requestCh.send(\"message\");\n      }\n\n      requestCh.close();\n    }).start();\n\n    final Histogram histogram = new Histogram(3600000000L, 3);\n\n    // Event recording, both HistHDR and logging\n    record(eventCh, new HdrHistogramRecorder(histogram, 1000000));\n\n    // Main\n    new Fiber<Void>(\"jbender\", () -> {\n      JBender.loadTestThroughput(intervalGenerator, 0, requestCh, requestExecutor, eventCh);\n      eventCh.close();\n    }).start().join();\n\n    // Avoid code elimination\n    return histogram;\n  }\n}\n"
  },
  {
    "path": "src/jmh/java/com/pinterest/jbender/JBenderHttpBenchmark.java",
    "content": "/*\nCopyright 2014 Pinterest.com\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n    http://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n */\npackage com.pinterest.jbender;\n\nimport co.paralleluniverse.fibers.Fiber;\nimport co.paralleluniverse.fibers.SuspendExecution;\nimport co.paralleluniverse.strands.channels.Channel;\nimport co.paralleluniverse.strands.channels.Channels;\nimport com.pinterest.jbender.events.TimingEvent;\nimport com.pinterest.jbender.events.recording.HdrHistogramRecorder;\nimport com.pinterest.jbender.executors.http.FiberApacheHttpClientRequestExecutor;\nimport com.pinterest.jbender.intervals.ConstantIntervalGenerator;\nimport com.pinterest.jbender.intervals.IntervalGenerator;\nimport org.HdrHistogram.Histogram;\nimport org.apache.http.client.methods.CloseableHttpResponse;\nimport org.apache.http.client.methods.HttpGet;\nimport org.openjdk.jmh.annotations.Benchmark;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.io.IOException;\nimport java.util.concurrent.ExecutionException;\n\nimport static com.pinterest.jbender.events.recording.Recorder.record;\n\n/**\n * Sample HTTP benchmark against {@url https://github.com/puniverse/comsat-gradle-template}\n */\npublic class JBenderHttpBenchmark {\n\n  @Benchmark\n  public Histogram loadtestHttpThroughput() throws SuspendExecution, InterruptedException, ExecutionException, IOException {\n    final IntervalGenerator intervalGenerator = new ConstantIntervalGenerator(10000000);\n    try (final FiberApacheHttpClientRequestExecutor requestExecutor =\n      new FiberApacheHttpClientRequestExecutor<>((res) -> {\n        if (res == null) {\n          throw new AssertionError(\"Response is null\");\n        }\n        final int status = res.getStatusLine().getStatusCode();\n        if (status != 200) {\n          throw new AssertionError(\"Status is \" + status);\n        }\n      }, 1000000)) {\n\n      final Channel<HttpGet> requestCh = Channels.newChannel(1000);\n      final Channel<TimingEvent<CloseableHttpResponse>> eventCh = Channels.newChannel(1000);\n\n      // Requests generator\n      new Fiber<Void>(\"req-gen\", () -> {\n        // Bench handling 1k reqs\n        for (int i = 0; i < 1000; ++i) {\n          requestCh.send(new HttpGet(\"http://localhost:8080/hello-world\"));\n        }\n\n        requestCh.close();\n      }).start();\n\n      final Histogram histogram = new Histogram(3600000000L, 3);\n\n      // Event recording, both HistHDR and logging\n      record(eventCh, new HdrHistogramRecorder(histogram, 1000000));\n\n      // Main\n      new Fiber<Void>(\"jbender\", () -> {\n        JBender.loadTestThroughput(intervalGenerator, 0, requestCh, requestExecutor, eventCh);\n        eventCh.close();\n      }).start().join();\n\n      // Avoid code elimination\n      return histogram;\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/com/pinterest/jbender/JBender.java",
    "content": "/*\nCopyright 2014 Pinterest.com\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n    http://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n */\npackage com.pinterest.jbender;\n\nimport co.paralleluniverse.fibers.Fiber;\nimport co.paralleluniverse.fibers.FiberScheduler;\nimport co.paralleluniverse.fibers.SuspendExecution;\nimport co.paralleluniverse.strands.*;\nimport co.paralleluniverse.strands.channels.ReceivePort;\nimport co.paralleluniverse.strands.channels.SendPort;\nimport co.paralleluniverse.strands.concurrent.Semaphore;\nimport com.pinterest.jbender.events.TimingEvent;\nimport com.pinterest.jbender.executors.RequestExecutor;\nimport com.pinterest.jbender.intervals.IntervalGenerator;\nimport com.pinterest.jbender.util.WaitGroup;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.util.concurrent.TimeUnit;\n\n/**\n * JBender has static methods for running load tests by throughput or concurrency.\n */\npublic final class JBender {\n  private static final Logger LOG = LoggerFactory.getLogger(JBender.class);\n\n  private JBender() {}\n\n  /**\n   * Run a load test with the given throughput, using as many fibers as necessary.\n   *\n   * This method can be run in any strand; thread-fiber synchronization is more expensive than\n   * fiber-fiber synchronization though, so if requests are being performed by fibers its best\n   * to call this method inside a fiber.\n   *\n   * @param intervalGen provides the interval between subsequent requests (in nanoseconds). This\n   *                    controls the throughput of the load test.\n   * @param warmupRequests the number of requests to use as \"warmup\" for the load tester and the\n   *                       service. These requests will not have TimingEvents generated in the\n   *                       eventChannel, but will be sent to the remote service at the requested\n   *                       rate.\n   * @param requests provides requests for the load test, must be closed by the caller to stop the\n   *                 load test (the load test will continue for as long as this channel is open,\n   *                 even if there are no requests arriving).\n   * @param executor executes the requests provided by the requests channel, returning a response\n   *                 object.\n   * @param eventChannel a TimingEvent is sent on this channel for every request executed during\n   *                     the load test (whether the request succeeds or not).\n   * @param <Req> the request type.\n   * @param <Res> the response type.\n   * @throws SuspendExecution\n   * @throws InterruptedException\n   */\n  public static <Req, Res> void loadTestThroughput(final IntervalGenerator intervalGen,\n                                                   final int warmupRequests,\n                                                   final ReceivePort<Req> requests,\n                                                   final RequestExecutor<Req, Res> executor,\n                                                   final SendPort<TimingEvent<Res>> eventChannel)\n    throws InterruptedException, SuspendExecution\n  {\n    loadTestThroughput(intervalGen, warmupRequests, requests, executor, eventChannel, null, null);\n  }\n\n  /**\n   * Run a load test with a given throughput, using as many fibers as necessary.\n   *\n   * This method can be run in any strand; thread-fiber synchronization is more expensive than\n   * fiber-fiber synchronization though, so if requests are being performed by fibers its best\n   * to call this method inside a fiber.\n   *\n   * @param intervalGen provides the interval between subsequent requests (in nanoseconds). This\n   *                    controls the throughput of the load test.\n   * @param warmupRequests the number of requests to use as \"warmup\" for the load tester and the\n   *                       service. These requests will not have TimingEvents generated in the\n   *                       eventChannel, but will be sent to the remote service at the requested\n   *                       rate.\n   * @param requests provides requests for the load test, must be closed by the caller to stop the\n   *                 load test (the load test will continue for as long as this channel is open,\n   *                 even if there are no requests arriving).\n   * @param executor executes the requests provided by the requests channel, returning a response\n   *                 object.\n   * @param eventChannel a TimingEvent is sent on this channel for every request executed during\n   *                     the load test (whether the request succeeds or not).\n   * @param fiberScheduler an optional scheduler for fibers that will perform the requests (the\n   *                       default one will be used if {@code null}).\n   * @param <Req> the request type.\n   * @param <Res> the response type.\n   * @throws SuspendExecution\n   * @throws InterruptedException\n   */\n  public static <Req, Res> void loadTestThroughput(final IntervalGenerator intervalGen,\n                                                   final int warmupRequests,\n                                                   final ReceivePort<Req> requests,\n                                                   final RequestExecutor<Req, Res> executor,\n                                                   final SendPort<TimingEvent<Res>> eventChannel,\n                                                   final FiberScheduler fiberScheduler)\n          throws InterruptedException, SuspendExecution\n  {\n    loadTestThroughput(intervalGen, warmupRequests, requests, executor, eventChannel, fiberScheduler, null);\n  }\n\n  /**\n   * Run a load test with a given throughput, using as many fibers as necessary.\n   *\n   * This method can be run in any strand; thread-fiber synchronization is more expensive than\n   * fiber-fiber synchronization though, so if requests are being performed by fibers its best\n   * to call this method inside a fiber.\n   *\n   * @param intervalGen provides the interval between subsequent requests (in nanoseconds). This\n   *                    controls the throughput of the load test.\n   * @param warmupRequests the number of requests to use as \"warmup\" for the load tester and the\n   *                       service. These requests will not have TimingEvents generated in the\n   *                       eventChannel, but will be sent to the remote service at the requested\n   *                       rate.\n   * @param requests provides requests for the load test, must be closed by the caller to stop the\n   *                 load test (the load test will continue for as long as this channel is open,\n   *                 even if there are no requests arriving).\n   * @param executor executes the requests provided by the requests channel, returning a response\n   *                 object.\n   * @param eventChannel a TimingEvent is sent on this channel for every request executed during\n   *                     the load test (whether the request succeeds or not).\n   * @param strandFactory an optional factory for strands that will perform the requests (the\n   *                      default one will be used if {@code null}).\n   * @param <Req> the request type.\n   * @param <Res> the response type.\n   * @throws SuspendExecution\n   * @throws InterruptedException\n   */\n  public static <Req, Res> void loadTestThroughput(final IntervalGenerator intervalGen,\n                                                   final int warmupRequests,\n                                                   final ReceivePort<Req> requests,\n                                                   final RequestExecutor<Req, Res> executor,\n                                                   final SendPort<TimingEvent<Res>> eventChannel,\n                                                   final StrandFactory strandFactory)\n          throws InterruptedException, SuspendExecution\n  {\n    loadTestThroughput(intervalGen, warmupRequests, requests, executor, eventChannel, null, strandFactory);\n  }\n\n  /**\n   * Run a load test with a given number of fibers, making as many requests as possible.\n   *\n   * This method can be run in any strand; thread-fiber synchronization is more expensive than\n   * fiber-fiber synchronization though, so if requests are being performed by fibers its best\n   * to call this method inside a fiber.\n   *\n   * @param concurrency the number of Fibers to run. Each Fiber will execute requests serially with\n   *                    as little overhead as possible.\n   * @param warmupRequests the number of requests to use when warming up the load tester and the\n   *                       remote service. These requests will not not have TimingEvents generated\n   *                       in the eventChannel, but will be sent to the remote service.\n   * @param requests provides requests for the load test and must be closed by the caller to stop\n   *                 the load test (the load test will continue for as long as this channel is\n   *                 open, even if there are no requests arriving).\n   * @param executor executes the requets provided by the requests channel, returning a response\n   *                 object.\n   * @param eventChannel a TimingEvent is sent on this channel for every request executed during\n   *                     the load test (whether the request succeeds or not).\n   * @param <Req> the request type.\n   * @param <Res> the response type.\n   * @throws SuspendExecution\n   * @throws InterruptedException\n   */\n  public static <Req, Res> void loadTestConcurrency(final int concurrency,\n                                                    final int warmupRequests,\n                                                    final ReceivePort<Req> requests,\n                                                    final RequestExecutor<Req, Res> executor,\n                                                    final SendPort<TimingEvent<Res>> eventChannel)\n    throws SuspendExecution, InterruptedException\n  {\n    loadTestConcurrency(concurrency, warmupRequests, requests, executor, eventChannel, null, null);\n  }\n\n  /**\n   * Run a load test with a given number of fibers, making as many requests as possible.\n   *\n   * This method can be run in any strand; thread-fiber synchronization is more expensive than\n   * fiber-fiber synchronization though, so if requests are being performed by fibers its best\n   * to call this method inside a fiber.\n   *\n   * @param concurrency the number of Fibers to run. Each Fiber will execute requests serially with\n   *                    as little overhead as possible.\n   * @param warmupRequests the number of requests to use when warming up the load tester and the\n   *                       remote service. These requests will not not have TimingEvents generated\n   *                       in the eventChannel, but will be sent to the remote service.\n   * @param requests provides requests for the load test and must be closed by the caller to stop\n   *                 the load test (the load test will continue for as long as this channel is\n   *                 open, even if there are no requests arriving).\n   * @param executor executes the requets provided by the requests channel, returning a response\n   *                 object.\n   * @param eventChannel a TimingEvent is sent on this channel for every request executed during\n   *                     the load test (whether the request succeeds or not).\n   * @param fiberScheduler an optional scheduler for fibers that will perform the requests (the\n   *                       default one will be used if {@code null}).\n   * @param <Req> the request type.\n   * @param <Res> the response type.\n   * @throws SuspendExecution\n   * @throws InterruptedException\n   */\n  public static <Req, Res> void loadTestConcurrency(final int concurrency,\n                                                    final int warmupRequests,\n                                                    final ReceivePort<Req> requests,\n                                                    final RequestExecutor<Req, Res> executor,\n                                                    final SendPort<TimingEvent<Res>> eventChannel,\n                                                    final FiberScheduler fiberScheduler)\n          throws SuspendExecution, InterruptedException\n  {\n    loadTestConcurrency(concurrency, warmupRequests, requests, executor, eventChannel, fiberScheduler, null);\n  }\n\n  /**\n   * Run a load test with a given number of fibers, making as many requests as possible.\n   *\n   * This method can be run in any strand; thread-fiber synchronization is more expensive than\n   * fiber-fiber synchronization though, so if requests are being performed by fibers its best\n   * to call this method inside a fiber.\n   *\n   * @param concurrency the number of Fibers to run. Each Fiber will execute requests serially with\n   *                    as little overhead as possible.\n   * @param warmupRequests the number of requests to use when warming up the load tester and the\n   *                       remote service. These requests will not not have TimingEvents generated\n   *                       in the eventChannel, but will be sent to the remote service.\n   * @param requests provides requests for the load test and must be closed by the caller to stop\n   *                 the load test (the load test will continue for as long as this channel is\n   *                 open, even if there are no requests arriving).\n   * @param executor executes the requets provided by the requests channel, returning a response\n   *                 object.\n   * @param eventChannel a TimingEvent is sent on this channel for every request executed during\n   *                     the load test (whether the request succeeds or not).\n   * @param strandFactory an optional factory for strands that will perform the requests (the\n   *                      default one will be used if {@code null}).\n   * @param <Req> the request type.\n   * @param <Res> the response type.\n   * @throws SuspendExecution\n   * @throws InterruptedException\n   */\n  public static <Req, Res> void loadTestConcurrency(final int concurrency,\n                                                    final int warmupRequests,\n                                                    final ReceivePort<Req> requests,\n                                                    final RequestExecutor<Req, Res> executor,\n                                                    final SendPort<TimingEvent<Res>> eventChannel,\n                                                    final StrandFactory strandFactory)\n          throws SuspendExecution, InterruptedException\n  {\n    loadTestConcurrency(concurrency, warmupRequests, requests, executor, eventChannel, null, strandFactory);\n  }\n\n  private static class RequestExecOutcome<Res> {\n    final long execTime;\n    final Res response;\n    final Exception exception;\n\n    public RequestExecOutcome(final long execTime, final Res response, final Exception exception) {\n      this.execTime = execTime;\n      this.response = response;\n      this.exception = exception;\n    }\n  }\n\n  private static <Req, Res> void loadTestThroughput(final IntervalGenerator intervalGen,\n                                                    int warmupRequests,\n                                                    final ReceivePort<Req> requests,\n                                                    final RequestExecutor<Req, Res> executor,\n                                                    final SendPort<TimingEvent<Res>> eventChannel,\n                                                    final FiberScheduler fiberScheduler,\n                                                    final StrandFactory strandFactory)\n          throws SuspendExecution, InterruptedException\n  {\n    final long startNanos = System.nanoTime();\n\n    try {\n      long overageNanos = 0;\n      long overageStart = System.nanoTime();\n\n      final WaitGroup waitGroup = new WaitGroup();\n      while (true) {\n        final long receiveNanosStart = System.nanoTime();\n        final Req request = requests.receive();\n        LOG.trace(\"Receive request time: {}\", System.nanoTime() - receiveNanosStart);\n        if (request == null) {\n          break;\n        }\n\n        // Wait before dispatching request as much as generated, minus the remaining dispatching overhead\n        // to be compensated for (up to having 0 waiting time of course, not negative)\n        long waitNanos = intervalGen.nextInterval(System.nanoTime() - startNanos);\n        final long adjust = Math.min(waitNanos, overageNanos);\n        waitNanos -= adjust;\n        overageNanos -= adjust;\n\n        // Sleep in the accepting fiber\n        long sleepNanosStart = System.nanoTime();\n        Strand.sleep(waitNanos, TimeUnit.NANOSECONDS);\n        LOG.trace(\"Sleep time: {}\", System.nanoTime() - sleepNanosStart);\n\n        // Increment wait group count for new request handler\n        waitGroup.add();\n        final long curWaitNanos = waitNanos;\n        final long curWarmupRequests = warmupRequests;\n        final long curOverageNanos = overageNanos;\n\n        final SuspendableCallable<Void> sc = () -> {\n          try {\n            final RequestExecOutcome<Res> outcome = executeRequest(request, executor);\n            if (curWarmupRequests <= 0) {\n              report(curWaitNanos, curOverageNanos, outcome, eventChannel);\n            }\n          } finally {\n            // Complete, decrementing wait group count\n            waitGroup.done();\n          }\n          return null;\n        };\n        if (fiberScheduler != null) {\n          new Fiber<>(fiberScheduler, sc).start();\n        } else if (strandFactory != null) {\n          strandFactory.newStrand(sc).start();\n        } else {\n          new Fiber<>(sc).start();\n        }\n\n        final long nowNanos = System.nanoTime();\n        overageNanos += nowNanos - overageStart - waitNanos;\n        overageStart = nowNanos;\n        warmupRequests = Math.max(warmupRequests - 1, 0);\n      }\n\n      // Wait for all outstanding requests\n      waitGroup.await();\n    } finally {\n      eventChannel.close();\n    }\n  }\n\n  private static <Req, Res> void loadTestConcurrency(final int concurrency,\n                                                     int warmupRequests,\n                                                     final ReceivePort<Req> requests,\n                                                     final RequestExecutor<Req, Res> executor,\n                                                     final SendPort<TimingEvent<Res>> eventChannel,\n                                                     final FiberScheduler fiberScheduler,\n                                                     final StrandFactory strandFactory)\n          throws SuspendExecution, InterruptedException\n  {\n    try {\n      final WaitGroup waitGroup = new WaitGroup();\n      final Semaphore running = new Semaphore(concurrency);\n\n      while (true) {\n        final Req request = requests.receive();\n        if (request == null) {\n          break;\n        }\n\n        running.acquire();\n        waitGroup.add();\n        final long curWarmupRequests = warmupRequests;\n        final SuspendableCallable<Void> sc = () -> {\n          try {\n            final RequestExecOutcome<Res> outcome = executeRequest(request, executor);\n            if (curWarmupRequests <= 0) {\n              report(0, 0, outcome, eventChannel);\n            }\n          } finally {\n            running.release();\n            waitGroup.done();\n          }\n          return null;\n        };\n        if (fiberScheduler != null) {\n          new Fiber<>(fiberScheduler, sc).start();\n        } else if (strandFactory != null) {\n          strandFactory.newStrand(sc).start();\n        } else {\n          new Fiber<>(sc).start();\n        }\n\n        warmupRequests = Math.max(warmupRequests - 1, 0);\n      }\n\n      waitGroup.await();\n    } finally {\n      eventChannel.close();\n    }\n  }\n\n  private static <Req, Res> RequestExecOutcome<Res> executeRequest(final Req request, final RequestExecutor<Req, Res> executor)\n          throws SuspendExecution, InterruptedException\n  {\n    Res response = null;\n    Exception exc = null;\n    final long startNanos = System.nanoTime();\n    try {\n      response = executor.execute(startNanos, request);\n    } catch (final Exception ex) {\n      LOG.error(\"Exception while executing request {}\", request, ex);\n      exc = ex;\n    }\n    return new RequestExecOutcome<>(System.nanoTime() - startNanos, response, exc);\n  }\n\n  private static <Res> void report(final long curWaitNanos,\n                                   long curOverageNanos,\n                                   final RequestExecOutcome<Res> outcome,\n                                   final SendPort<TimingEvent<Res>> eventChannel)\n      throws SuspendExecution, InterruptedException\n  {\n    if (outcome.exception == null) {\n      eventChannel.send(\n          new TimingEvent<>(curWaitNanos, outcome.execTime, curOverageNanos, outcome.response));\n    } else {\n      eventChannel.send(\n          new TimingEvent<>(curWaitNanos, outcome.execTime, curOverageNanos, outcome.exception));\n    }\n  }\n}\n"
  },
  {
    "path": "src/main/java/com/pinterest/jbender/events/TimingEvent.java",
    "content": "/*\nCopyright 2014 Pinterest.com\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n    http://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n */\npackage com.pinterest.jbender.events;\n\nimport com.google.common.base.MoreObjects;\n\n/**\n * TimingEvents collect timing information and result data for a single request from the load\n * tester.\n *\n * @param <T> the response type from the service being tested.\n */\npublic class TimingEvent<T> {\n  public final long waitNanos;\n  public final long durationNanos;\n  public final long overageNanos;\n  public final boolean isSuccess;\n  public final Exception exception;\n  public final T response;\n\n  private TimingEvent(final long waitNanos,\n                      final long durationNanos,\n                      long overageNanos,\n                      final T response,\n                      final Exception exc)\n  {\n    this.response = response;\n    this.exception = exc;\n    this.waitNanos = waitNanos;\n    this.durationNanos = durationNanos;\n    this.overageNanos = overageNanos;\n    this.isSuccess = exc == null;\n  }\n\n  public TimingEvent(\n      final long waitNanos, final long durationNanos, long overageNanos, final T response) {\n    this(waitNanos, durationNanos, overageNanos, response, null);\n  }\n\n  public TimingEvent(\n      final long waitNanos, final long durationNanos, long overageNanos, final Exception exc) {\n    this(waitNanos, durationNanos, overageNanos, null, exc);\n  }\n\n  @Override\n  public String toString() {\n    return MoreObjects.toStringHelper(this)\n        .add(\"waitNanos\", waitNanos)\n        .add(\"durationNanos\", durationNanos)\n        .add(\"overageNanos\", overageNanos)\n        .add(\"isSuccess\", isSuccess)\n        .add(\"exception\", exception)\n        .add(\"response\", response)\n        .toString();\n  }\n}\n"
  },
  {
    "path": "src/main/java/com/pinterest/jbender/events/recording/HdrHistogramRecorder.java",
    "content": "/*\nCopyright 2014 Pinterest.com\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n    http://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n */\npackage com.pinterest.jbender.events.recording;\n\nimport com.pinterest.jbender.events.TimingEvent;\nimport org.HdrHistogram.Histogram;\n\n/**\n * Records the duration of each TimingEvent in an HdrHistogram and keeps track of the total number\n * of errors and the start and end time of the load test.\n */\npublic class HdrHistogramRecorder implements Recorder {\n  private final long scale;\n  private boolean started;\n\n  // The histogram used to record durations\n  public final Histogram histogram;\n\n  // The number of errors seen by this recorder so far.\n  public long errorCount;\n\n  // The start time of the first TimingEvent seen by this recorder. This is an estimate of the\n  // start time of the load test, and not exact.\n  public long startNanos;\n\n  // The end time of the latest TimingEvent seen by this recorder.\n  public long endNanos;\n\n  /**\n   * Constructor.\n   *\n   * @param h the HdrHistogram object into which values are written.\n   * @param scale the value by which to divide the durationNanos field of each TimingEvent before\n   *              recording it in the histogram. For example, to record milliseconds you would set\n   *              the scale to 1,000,000, for microseconds you would use 1,000, and so on.\n   */\n  public HdrHistogramRecorder(final Histogram h, long scale) {\n    this.histogram = h;\n    this.scale = scale;\n    this.errorCount = 0;\n    this.startNanos = System.nanoTime();\n    this.endNanos = System.nanoTime();\n    this.started = false;\n  }\n\n  @Override\n  public void record(final TimingEvent e) {\n    if (!started) {\n      started = true;\n      startNanos = System.nanoTime() - e.durationNanos;\n    }\n\n    if (!e.isSuccess) {\n      errorCount++;\n    }\n\n    histogram.recordValue(e.durationNanos / scale);\n\n    endNanos = System.nanoTime();\n  }\n}\n"
  },
  {
    "path": "src/main/java/com/pinterest/jbender/events/recording/LoggingRecorder.java",
    "content": "/*\nCopyright 2014 Pinterest.com\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n    http://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n */\npackage com.pinterest.jbender.events.recording;\n\nimport com.pinterest.jbender.events.TimingEvent;\nimport org.slf4j.Logger;\n\n/**\n * A very simple Recorder that logs each TimingEvent at the INFO level, using the\n * TimingEvent#toString method.\n */\npublic class LoggingRecorder implements Recorder {\n  private final Logger log;\n\n  public LoggingRecorder(final Logger l) {\n    this.log = l;\n  }\n\n  @Override\n  public void record(final TimingEvent e) {\n    log.info(e.toString());\n  }\n}\n"
  },
  {
    "path": "src/main/java/com/pinterest/jbender/events/recording/Recorder.java",
    "content": "/*\nCopyright 2014 Pinterest.com\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n    http://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n */\npackage com.pinterest.jbender.events.recording;\n\nimport co.paralleluniverse.fibers.DefaultFiberScheduler;\nimport co.paralleluniverse.fibers.Fiber;\nimport co.paralleluniverse.fibers.FiberScheduler;\nimport co.paralleluniverse.strands.channels.ReceivePort;\nimport com.pinterest.jbender.events.TimingEvent;\n\n/**\n * Event recorder interface and main recording facility.\n */\n@FunctionalInterface\npublic interface Recorder<T> {\n  void record(TimingEvent<T> e);\n\n  /**\n   * Record events in a separate Fiber using one or more Recorders.\n   *\n   * This method returns immediately after starting the Fiber to record events.\n   *\n   * @param rp a channel on which to receive TimingEvents, which are passed to the given recorders\n   *           in the order specified.\n   * @param rs a list of one or more recorders to process each TimingEvent.\n   * @param <T> the type of the Response object in each TimingEvent.\n   */\n  @SafeVarargs\n  static <T> Fiber<Void> record(final ReceivePort<TimingEvent<T>> rp, final Recorder<T>... rs) {\n    return record(\"jbender-recorder\", null, rp, rs);\n  }\n\n  /**\n   * Record events in a separate Fiber using one or more Recorders.\n   *\n   * This method returns immediately after starting the Fiber for recording events.\n   *\n   * @param fiberName the name of the fiber that records events.\n   * @param fe an optional scheduler for the spawned recording fiber, null to use the default.\n   * @param rp a channel on which to receive TimingEvents, which are passed to the underlying\n   *           Recorders.\n   * @param rs zero or more Recorders, each of which receives every event (after the delay period)\n   *           in the order they are passed to this method.\n   * @param <T> the type of the Response object in each TimingEvent.\n   */\n  @SafeVarargs\n  static <T> Fiber<Void> record(final String fiberName,\n                                final FiberScheduler fe,\n                                final ReceivePort<TimingEvent<T>> rp,\n                                final Recorder<T>... rs)\n  {\n    return new Fiber<Void>(fiberName, fe != null ? fe : DefaultFiberScheduler.getInstance(), () -> {\n      while (true) {\n        final TimingEvent<T> event = rp.receive();\n\n        if (event == null) {\n          break;\n        }\n\n        for (final Recorder<T> r : rs) {\n          r.record(event);\n        }\n      }\n      return null;\n    }).start();\n  }\n}\n"
  },
  {
    "path": "src/main/java/com/pinterest/jbender/executors/NoopRequestExecutor.java",
    "content": "/*\nCopyright 2014 Pinterest.com\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n    http://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n */\npackage com.pinterest.jbender.executors;\n\nimport co.paralleluniverse.fibers.SuspendExecution;\n\npublic class NoopRequestExecutor<Q> implements RequestExecutor<Q, Void> {\n  @Override\n  public Void execute(final long nanoTime, final Q request) throws SuspendExecution, InterruptedException {\n    // NOP\n    return null;\n  }\n}\n"
  },
  {
    "path": "src/main/java/com/pinterest/jbender/executors/RequestExecutor.java",
    "content": "/*\nCopyright 2014 Pinterest.com\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n    http://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n */\npackage com.pinterest.jbender.executors;\n\nimport co.paralleluniverse.fibers.SuspendExecution;\n\n/**\n * Request executor interface.\n *\n * @param <Q> The request class.\n * @param <S> The response class.\n */\npublic interface RequestExecutor<Q, S> {\n  /**\n   * The suspendable request execution logic.\n   *\n   * @param nanoTime The request execution start time.\n   * @param request  The request to be executed.\n   *\n   * @return The response value.\n   */\n  S execute(long nanoTime, Q request) throws SuspendExecution, InterruptedException;\n}\n"
  },
  {
    "path": "src/main/java/com/pinterest/jbender/executors/SleepyRequestExecutor.java",
    "content": "/*\nCopyright 2014 Pinterest.com\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n    http://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n */\npackage com.pinterest.jbender.executors;\n\nimport co.paralleluniverse.fibers.SuspendExecution;\nimport co.paralleluniverse.strands.Strand;\n\npublic class SleepyRequestExecutor<Q> implements RequestExecutor<Q, Void> {\n  private final int sleepMillis;\n  private final int sleepNanos;\n\n  public SleepyRequestExecutor(int sleepMillis, int sleepNanos) {\n    this.sleepMillis = sleepMillis;\n    this.sleepNanos = sleepNanos;\n  }\n\n  @Override\n  public Void execute(final long nanoTime, final Q request) throws SuspendExecution, InterruptedException {\n    Strand.sleep(sleepMillis, sleepNanos);\n    return null;\n  }\n}\n"
  },
  {
    "path": "src/main/java/com/pinterest/jbender/executors/Validator.java",
    "content": "/*\nCopyright 2014 Pinterest.com\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n    http://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n */\npackage com.pinterest.jbender.executors;\n\n/**\n * Validator of {@code T} values.\n *\n * @param <T> The value class.\n */\n@FunctionalInterface\npublic interface Validator<T> {\n  /**\n   * Will throw an unchecked exception if validation failes\n   */\n  void validate(final T value);\n}\n"
  },
  {
    "path": "src/main/java/com/pinterest/jbender/executors/http/FiberApacheHttpClientRequestExecutor.java",
    "content": "/*\nCopyright 2014 Pinterest.com\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n    http://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n */\npackage com.pinterest.jbender.executors.http;\n\nimport co.paralleluniverse.common.util.Exceptions;\nimport co.paralleluniverse.fibers.Fiber;\nimport co.paralleluniverse.fibers.SuspendExecution;\nimport co.paralleluniverse.fibers.httpclient.FiberHttpClient;\nimport co.paralleluniverse.strands.SuspendableCallable;\nimport com.pinterest.jbender.executors.RequestExecutor;\nimport com.pinterest.jbender.executors.Validator;\nimport org.apache.http.client.config.RequestConfig;\nimport org.apache.http.client.methods.CloseableHttpResponse;\nimport org.apache.http.client.methods.HttpRequestBase;\nimport org.apache.http.impl.nio.client.CloseableHttpAsyncClient;\nimport org.apache.http.impl.nio.client.HttpAsyncClientBuilder;\nimport org.apache.http.impl.nio.conn.PoolingNHttpClientConnectionManager;\nimport org.apache.http.impl.nio.reactor.DefaultConnectingIOReactor;\nimport org.apache.http.impl.nio.reactor.IOReactorConfig;\nimport org.apache.http.nio.reactor.IOReactorException;\n\nimport java.io.IOException;\nimport java.util.concurrent.ExecutionException;\n\n/**\n * Executor base class offering a Comsat-based implementation of an HTTP request executor.\n */\npublic class FiberApacheHttpClientRequestExecutor<X extends HttpRequestBase> implements RequestExecutor<X, CloseableHttpResponse>, AutoCloseable {\n  // Inspired by https://github.com/puniverse/photon/blob/master/src/main/java/co/paralleluniverse/photon/Photon.java\n\n  private final Validator<CloseableHttpResponse> validator;\n  private final FiberHttpClient client;\n\n  public FiberApacheHttpClientRequestExecutor(final Validator<CloseableHttpResponse> resValidator, final int maxConnections, final int timeout, final int parallelism) throws IOReactorException {\n    final DefaultConnectingIOReactor ioreactor = new DefaultConnectingIOReactor(IOReactorConfig.custom().\n      setConnectTimeout(timeout).\n      setIoThreadCount(parallelism).\n      setSoTimeout(timeout).\n      build());\n\n    final PoolingNHttpClientConnectionManager mngr = new PoolingNHttpClientConnectionManager(ioreactor);\n    mngr.setDefaultMaxPerRoute(maxConnections);\n    mngr.setMaxTotal(maxConnections);\n\n    final CloseableHttpAsyncClient ahc = HttpAsyncClientBuilder.create().\n      setConnectionManager(mngr).\n      setDefaultRequestConfig(RequestConfig.custom().setLocalAddress(null).build()).build();\n\n    client = new FiberHttpClient(ahc);\n    validator = resValidator;\n  }\n\n  public FiberApacheHttpClientRequestExecutor(final Validator<CloseableHttpResponse> resValidator, final int maxConnections, final int timeout) throws IOReactorException {\n    this(resValidator, maxConnections, timeout, Runtime.getRuntime().availableProcessors());\n  }\n\n  public FiberApacheHttpClientRequestExecutor(final Validator<CloseableHttpResponse> resValidator, final int maxConnections) throws IOReactorException {\n    this(resValidator, maxConnections, 0);\n  }\n\n  public FiberApacheHttpClientRequestExecutor(final int maxConnections) throws IOReactorException {\n    this(null, maxConnections, 0);\n  }\n\n  // TODO Figure out meaningful and sensible default for maxConnections and add no-args constructor\n\n  @Override\n  public CloseableHttpResponse execute(final long nanoTime, final HttpRequestBase request) throws SuspendExecution, InterruptedException {\n    // TODO See if timeout can be configured per-request\n    final CloseableHttpResponse ret;\n    try {\n      ret = new Fiber<>((SuspendableCallable<CloseableHttpResponse>) () -> {\n        try {\n          return client.execute(request);\n        } catch (final IOException e) {\n          throw Exceptions.rethrowUnwrap(e);\n        }\n      }).start().get();\n    } catch (final ExecutionException e) {\n      throw Exceptions.rethrowUnwrap(e);\n    }\n    if (validator != null) {\n      validator.validate(ret);\n    }\n    return ret;\n  }\n\n  @Override\n  public void close() throws IOException {\n    client.close();\n  }\n}\n"
  },
  {
    "path": "src/main/java/com/pinterest/jbender/intervals/ConstantIntervalGenerator.java",
    "content": "/*\nCopyright 2014 Pinterest.com\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n    http://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n */\npackage com.pinterest.jbender.intervals;\n\npublic class ConstantIntervalGenerator implements IntervalGenerator {\n  private final long interval;\n\n  public ConstantIntervalGenerator(long interval) {\n    this.interval = interval;\n  }\n\n  @Override\n  public long nextInterval(long nanoTimeSinceStart) {\n    return interval;\n  }\n}\n"
  },
  {
    "path": "src/main/java/com/pinterest/jbender/intervals/ExponentialIntervalGenerator.java",
    "content": "/*\nCopyright 2014 Pinterest.com\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n    http://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n */\npackage com.pinterest.jbender.intervals;\n\nimport java.util.Random;\n\n/**\n * Poisson distribution request interval generator.\n */\npublic class ExponentialIntervalGenerator implements IntervalGenerator {\n  private final double nanosPerQuery;\n  private final Random rand;\n\n  public ExponentialIntervalGenerator(int queriesPerSecond) {\n    nanosPerQuery = 1000000000.0 / queriesPerSecond;\n    this.rand = new Random();\n  }\n\n  @Override\n  public long nextInterval(long nanoTimeSinceStart) {\n    return (long) (-Math.log(rand.nextDouble()) * this.nanosPerQuery);\n  }\n}\n"
  },
  {
    "path": "src/main/java/com/pinterest/jbender/intervals/IntervalGenerator.java",
    "content": "/*\nCopyright 2014 Pinterest.com\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n    http://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n */\npackage com.pinterest.jbender.intervals;\n\n/**\n * Request interval duration generator interface.\n */\n@FunctionalInterface\npublic interface IntervalGenerator {\n  long nextInterval(long nanoTimeSinceStart);\n}\n"
  },
  {
    "path": "src/main/java/com/pinterest/jbender/util/ConnectionPool.java",
    "content": "/*\nCopyright 2014 Pinterest.com\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n    http://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n */\npackage com.pinterest.jbender.util;\n\nimport co.paralleluniverse.fibers.SuspendExecution;\nimport co.paralleluniverse.strands.channels.Channel;\nimport co.paralleluniverse.strands.channels.Channels;\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\n\nimport java.io.Closeable;\nimport java.io.IOException;\n\n/**\n * An inconvenient ConnectionPool for sockets that are closeable.\n *\n * Use will typically look like this:\n *\n * <code>\n *  ConnectionPool<T> pool = new ConnectionPool(...);\n *  T t = null;\n *  try {\n *    t = pool.acquire();\n *    ...\n *    pool.release(t);\n *  } catch (Exception ex) {\n *    try {\n *      pool.releaseAfterError(t);\n *    } catch (IOException ioex) {\n *      LOG.warn(\"Failed to close connection\", ioex);\n *    }\n *  }\n * </code>\n *\n * @param <T>\n */\npublic class ConnectionPool<T extends Closeable> {\n  @FunctionalInterface\n  public interface SuspendableSupplierWithIO<T> {\n    T get() throws IOException, SuspendExecution;\n  }\n\n  private final SuspendableSupplierWithIO<T> supplier;\n  private final Channel<T> pool;\n\n  public ConnectionPool(SuspendableSupplierWithIO<T> supplier, int maxPoolSize) {\n    this.supplier = supplier;\n    pool = Channels.newChannel(maxPoolSize, Channels.OverflowPolicy.BLOCK, false, false);\n  }\n\n  public T acquire() throws IOException, SuspendExecution {\n    T t = pool.tryReceive();\n    if (t == null) {\n      t = supplier.get();\n    }\n    return t;\n  }\n\n  public void release(T t) throws IOException {\n    if (t == null) {\n      return;\n    }\n\n    boolean released = pool.trySend(t);\n    if (!released) {\n      t.close();\n    }\n  }\n\n  public void releaseAfterError(T t) throws IOException {\n    if (t == null) {\n      return;\n    }\n\n    t.close();\n  }\n}\n"
  },
  {
    "path": "src/main/java/com/pinterest/jbender/util/ListReceivePort.java",
    "content": "/*\nCopyright 2014 Pinterest.com\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n    http://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n */\npackage com.pinterest.jbender.util;\n\nimport co.paralleluniverse.fibers.SuspendExecution;\nimport co.paralleluniverse.strands.Timeout;\nimport co.paralleluniverse.strands.channels.ReceivePort;\n\nimport java.util.List;\nimport java.util.concurrent.TimeUnit;\n\npublic class ListReceivePort<T> implements ReceivePort<T> {\n  private final List<T> list;\n  private final int total;\n  private int cur;\n\n  public ListReceivePort(List<T> list) {\n    this.list = list;\n    total = list.size();\n    cur = 0;\n  }\n\n  public ListReceivePort(List<T> list, int total) {\n    this.list = list;\n    this.total = total;\n    cur = 0;\n  }\n\n  @Override\n  public T receive() throws SuspendExecution, InterruptedException {\n    if (cur >= total) {\n      return null;\n    }\n\n    return list.get(cur++ % list.size());\n  }\n\n  @Override\n  public T receive(long timeout, TimeUnit unit) throws SuspendExecution, InterruptedException {\n    return receive();\n  }\n\n  @Override\n  public T receive(Timeout timeout) throws SuspendExecution, InterruptedException {\n    return receive();\n  }\n\n  @Override\n  public T tryReceive() {\n    if (cur >= total) {\n      return null;\n    }\n\n    return list.get(cur++ % list.size());\n  }\n\n  @Override\n  public void close() {}\n\n  @Override\n  public boolean isClosed() {\n    return false;\n  }\n}\n"
  },
  {
    "path": "src/main/java/com/pinterest/jbender/util/WaitGroup.java",
    "content": "/*\nCopyright 2014 Pinterest.com\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n    http://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n */\npackage com.pinterest.jbender.util;\n\nimport java.util.concurrent.atomic.AtomicLong;\nimport co.paralleluniverse.fibers.SuspendExecution;\nimport co.paralleluniverse.strands.Strand;\n\n/**\n * Simplified phaser supporting up to {@code Long.MAX_VALUE} fibers.\n */\npublic class WaitGroup {\n  private volatile Strand waiter;\n  private AtomicLong running;\n\n  public WaitGroup() {\n    running = new AtomicLong();\n    waiter = null;\n  }\n\n  public void add() {\n    running.incrementAndGet();\n  }\n\n  public void done() {\n    long count = running.decrementAndGet();\n    if (count == 0 && waiter != null) {\n      waiter.unpark();\n    }\n  }\n\n  public void await() throws SuspendExecution {\n    waiter = Strand.currentStrand();\n    while (running.get() > 0) {\n      Strand.park();\n    }\n  }\n}\n"
  },
  {
    "path": "src/test/java/com/pinterest/jbender/JBenderTest.java",
    "content": "/*\nCopyright 2014 Pinterest.com\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n    http://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n */\npackage com.pinterest.jbender;\n\nimport co.paralleluniverse.fibers.Fiber;\nimport co.paralleluniverse.fibers.SuspendExecution;\nimport co.paralleluniverse.strands.channels.Channel;\nimport co.paralleluniverse.strands.channels.Channels;\nimport com.google.common.collect.Sets;\nimport com.pinterest.jbender.events.TimingEvent;\nimport com.pinterest.jbender.executors.RequestExecutor;\nimport com.pinterest.jbender.intervals.ConstantIntervalGenerator;\nimport com.pinterest.jbender.intervals.IntervalGenerator;\nimport org.junit.Test;\n\nimport java.util.Set;\n\nimport static org.junit.Assert.assertEquals;\n\npublic class JBenderTest {\n  private static final class FakeRequestExecutor implements RequestExecutor<Integer, Integer> {\n    @Override\n    public Integer execute(long nanoTime, Integer request) throws SuspendExecution, InterruptedException {\n      return request;\n    }\n  }\n\n  private void assertEvents(Channel<TimingEvent<Integer>> eventCh, int start, int end) throws SuspendExecution, InterruptedException {\n    Set<Integer> actual = Sets.newHashSetWithExpectedSize(end - start);\n    Set<Integer> expected = Sets.newHashSetWithExpectedSize(end - start);\n\n    for (int i = start; i < end; ++i) {\n      expected.add(i);\n    }\n\n    int cur = start;\n    while (true) {\n      TimingEvent<Integer> t = eventCh.receive();\n\n      if (t == null) {\n        break;\n      }\n\n      Integer i = t.response;\n      actual.add(i);\n      cur++;\n    }\n\n    assertEquals(cur, end);\n    assertEquals(actual, expected);\n  }\n\n  private void requests(Channel<Integer> requestCh, int count) {\n    new Fiber<Void>(() -> {\n      for (int i = 0; i < count; ++i) {\n        requestCh.send(i);\n      }\n      requestCh.close();\n    }).start();\n  }\n\n  @Test\n  public void testLoadTestThroughputNoRequests() throws SuspendExecution, InterruptedException {\n    IntervalGenerator intervalGen = new ConstantIntervalGenerator(0);\n    Channel<Integer> requestCh = Channels.newChannel(-1);\n    Channel<TimingEvent<Integer>> eventCh = Channels.newChannel(-1);\n    RequestExecutor<Integer, Integer> executor = new FakeRequestExecutor();\n\n    requests(requestCh, 0);\n    JBender.loadTestThroughput(intervalGen, 0, requestCh, executor, eventCh);\n    assertEvents(eventCh, 0, 0);\n  }\n\n  @Test\n  public void testLoadTestThroughputOneRequest() throws SuspendExecution, InterruptedException {\n    IntervalGenerator intervalGen = new ConstantIntervalGenerator(0);\n    Channel<Integer> requestCh = Channels.newChannel(-1);\n    Channel<TimingEvent<Integer>> eventCh = Channels.newChannel(-1);\n    RequestExecutor<Integer, Integer> executor = new FakeRequestExecutor();\n\n    requests(requestCh, 1);\n    JBender.loadTestThroughput(intervalGen, 0, requestCh, executor, eventCh);\n    assertEvents(eventCh, 0, 1);\n  }\n\n  @Test\n  public void testLoadTestThroughputTenRequests() throws SuspendExecution, InterruptedException {\n    IntervalGenerator intervalGen = new ConstantIntervalGenerator(0);\n    Channel<Integer> requestCh = Channels.newChannel(-1);\n    Channel<TimingEvent<Integer>> eventCh = Channels.newChannel(-1);\n    RequestExecutor<Integer, Integer> executor = new FakeRequestExecutor();\n\n    requests(requestCh, 10);\n    JBender.loadTestThroughput(intervalGen, 0, requestCh, executor, eventCh);\n    assertEvents(eventCh, 0, 10);\n  }\n\n  @Test\n  public void testLoadTestThroughputWarmup() throws SuspendExecution, InterruptedException {\n    IntervalGenerator intervalGen = new ConstantIntervalGenerator(0);\n    Channel<Integer> requestCh = Channels.newChannel(-1);\n    Channel<TimingEvent<Integer>> eventCh = Channels.newChannel(-1);\n    RequestExecutor<Integer, Integer> executor = new FakeRequestExecutor();\n\n    requests(requestCh, 10);\n    JBender.loadTestThroughput(intervalGen, 5, requestCh, executor, eventCh);\n    assertEvents(eventCh, 5, 10);\n  }\n\n  @Test\n  public void testLoadTestConcurrencyNoRequests() throws SuspendExecution, InterruptedException {\n    Channel<Integer> requestCh = Channels.newChannel(-1);\n    Channel<TimingEvent<Integer>> eventCh = Channels.newChannel(-1);\n    RequestExecutor<Integer, Integer> executor = new FakeRequestExecutor();\n\n    requests(requestCh, 0);\n    JBender.loadTestConcurrency(1, 0, requestCh, executor, eventCh);\n    assertEvents(eventCh, 0, 0);\n  }\n\n  @Test\n  public void testLoadTestConcurrencyOneRequest() throws SuspendExecution, InterruptedException {\n    Channel<Integer> requestCh = Channels.newChannel(-1);\n    Channel<TimingEvent<Integer>> eventCh = Channels.newChannel(-1);\n    RequestExecutor<Integer, Integer> executor = new FakeRequestExecutor();\n\n    requests(requestCh, 1);\n    JBender.loadTestConcurrency(1, 0, requestCh, executor, eventCh);\n    assertEvents(eventCh, 0, 1);\n  }\n\n  @Test\n  public void testLoadTestConcurrencyTenRequests() throws SuspendExecution, InterruptedException {\n    Channel<Integer> requestCh = Channels.newChannel(-1);\n    Channel<TimingEvent<Integer>> eventCh = Channels.newChannel(-1);\n    RequestExecutor<Integer, Integer> executor = new FakeRequestExecutor();\n\n    requests(requestCh, 10);\n    JBender.loadTestConcurrency(1, 0, requestCh, executor, eventCh);\n    assertEvents(eventCh, 0, 10);\n  }\n\n  @Test\n  public void testLoadTestConcurrencyWarmup() throws SuspendExecution, InterruptedException {\n    Channel<Integer> requestCh = Channels.newChannel(-1);\n    Channel<TimingEvent<Integer>> eventCh = Channels.newChannel(-1);\n    RequestExecutor<Integer, Integer> executor = new FakeRequestExecutor();\n\n    requests(requestCh, 10);\n    JBender.loadTestConcurrency(1, 5, requestCh, executor, eventCh);\n    assertEvents(eventCh, 5, 10);\n  }\n}\n"
  }
]