├── .github └── workflows │ ├── codeql-analysis.yml │ └── test.yml ├── .gitignore ├── .travis.yml ├── CHANGES.markdown ├── LICENSE ├── README.markdown ├── SECURITY.md ├── metrics2-riemann-reporter ├── pom.xml └── src │ ├── main │ └── java │ │ └── com │ │ └── yammer │ │ └── metrics │ │ └── reporting │ │ └── RiemannReporter.java │ └── test │ └── java │ └── riemann │ └── java │ └── client │ └── tests │ └── RiemannReporterConfigTest.java ├── metrics3-riemann-reporter ├── pom.xml └── src │ ├── main │ └── java │ │ └── com │ │ └── codahale │ │ └── metrics │ │ └── riemann │ │ ├── Riemann.java │ │ ├── RiemannReporter.java │ │ ├── ValueFilter.java │ │ └── ValueFilterMap.java │ └── test │ └── java │ └── com │ └── codahale │ └── metrics │ └── riemann │ ├── RiemannReporterTest.java │ ├── RiemannTest.java │ └── ValueFilterTest.java ├── metrics4-riemann-reporter ├── pom.xml └── src │ ├── main │ └── java │ │ └── com │ │ └── codahale │ │ └── metrics │ │ └── riemann │ │ ├── Riemann.java │ │ ├── RiemannReporter.java │ │ ├── ValueFilter.java │ │ └── ValueFilterMap.java │ └── test │ └── java │ └── com │ └── codahale │ └── metrics │ └── riemann │ ├── RiemannReporterTest.java │ ├── RiemannTest.java │ └── ValueFilterTest.java ├── pom.xml ├── riemann-java-client ├── pom.xml └── src │ ├── main │ ├── java │ │ ├── clojure │ │ │ └── lang │ │ │ │ ├── IBlockingDeref.java │ │ │ │ └── IDeref.java │ │ └── io │ │ │ └── riemann │ │ │ └── riemann │ │ │ └── client │ │ │ ├── AsynchronizeTransport.java │ │ │ ├── AsynchronousTransport.java │ │ │ ├── ChainPromise.java │ │ │ ├── EventBuilder.java │ │ │ ├── EventDSL.java │ │ │ ├── ExceptionReporter.java │ │ │ ├── Fn2.java │ │ │ ├── IPromise.java │ │ │ ├── IRiemannClient.java │ │ │ ├── MapPromise.java │ │ │ ├── MsgTooLargeException.java │ │ │ ├── MsgValidator.java │ │ │ ├── Null.java │ │ │ ├── OverloadedException.java │ │ │ ├── Promise.java │ │ │ ├── ReconnectHandler.java │ │ │ ├── RiemannBatchClient.java │ │ │ ├── RiemannClient.java │ │ │ ├── RiemannScheduler.java │ │ │ ├── SSL.java │ │ │ ├── ServerError.java │ │ │ ├── SimpleUdpTransport.java │ │ │ ├── SynchronousTransport.java │ │ │ ├── TcpHandler.java │ │ │ ├── TcpTransport.java │ │ │ ├── Transport.java │ │ │ ├── UdpTransport.java │ │ │ ├── UnsupportedJVMException.java │ │ │ ├── Write.java │ │ │ └── WriteQueue.java │ └── proto │ │ └── riemann │ │ └── proto.proto │ └── test │ └── java │ └── riemann │ └── java │ └── client │ └── tests │ ├── BatchClientPromiseTest.java │ ├── ChainPromiseTest.java │ ├── EchoServer.java │ ├── EventBuilderTest.java │ ├── OkServer.java │ ├── PromiseTest.java │ ├── Server.java │ ├── SimpleUdpClientTest.java │ ├── TcpClientTest.java │ └── Util.java └── scripts └── install-protobuf.sh /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ master ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ master ] 20 | schedule: 21 | - cron: '17 9 * * 0' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'java' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] 37 | # Learn more about CodeQL language support at https://git.io/codeql-language-support 38 | 39 | steps: 40 | - name: Checkout repository 41 | uses: actions/checkout@v2 42 | 43 | # Initializes the CodeQL tools for scanning. 44 | - name: Initialize CodeQL 45 | uses: github/codeql-action/init@v1 46 | with: 47 | languages: ${{ matrix.language }} 48 | # If you wish to specify custom queries, you can do so here or in a config file. 49 | # By default, queries listed here will override any specified in a config file. 50 | # Prefix the list here with "+" to use these queries and those in the config file. 51 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 52 | 53 | # ℹ️ Command-line programs to run using the OS shell. 54 | # 📚 ht 55 | 56 | - name: Installed protobuf 57 | run: | 58 | ./scripts/install-protobuf.sh 59 | 60 | - name: Add protoc to path 61 | run: | 62 | echo "$HOME/bin" >> $GITHUB_PATH 63 | 64 | - name: install deps 65 | run: | 66 | mvn -DskipTests clean install dependency:resolve-plugins dependency:go-offline 67 | 68 | - name: Perform CodeQL Analysis 69 | uses: github/codeql-action/analyze@v1 70 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | 2 | name: Riemann Clojure Client testing 3 | 4 | on: 5 | push: 6 | workflow_dispatch: 7 | 8 | jobs: 9 | test: 10 | 11 | strategy: 12 | matrix: 13 | java: [ '8', '11', '13', '15', '16', '17' ] 14 | fail-fast: false 15 | name: Java ${{ matrix.Java }} 16 | 17 | runs-on: ubuntu-latest 18 | 19 | env: 20 | JVM_OPTS: -Xmx3200m 21 | 22 | steps: 23 | - uses: actions/checkout@v3 24 | - name: Setup Java 25 | uses: actions/setup-java@v3 26 | with: 27 | distribution: 'zulu' 28 | java-version: ${{ matrix.java }} 29 | - name: Install protobuf 30 | run: | 31 | cd /tmp 32 | wget https://github.com/google/protobuf/releases/download/v3.16.1/protoc-3.16.1-linux-x86_64.zip 33 | unzip protoc-3.16.1-linux-x86_64.zip 34 | mv /tmp/bin/protoc /usr/local/bin/ 35 | protoc --version 36 | - name: Install deps 37 | run: mvn -DskipTests clean install dependency:resolve-plugins dependency:go-offline 38 | - name: Cache dependencies 39 | uses: actions/cache@v3 40 | with: 41 | path: ~/.m2/repository 42 | key: ${{ matrix.java }}-maven-${{ hashFiles('**/pom.xml') }} 43 | - name: Run tests 44 | run: mvn package 45 | - name: Upload Test Results 46 | if: always() 47 | uses: actions/upload-artifact@v3 48 | with: 49 | name: Test Results (Java ${{ matrix.java }}) 50 | path: '**/target/surefire-reports/' 51 | 52 | publish-test-results: 53 | name: "Publish Tests Results" 54 | needs: test 55 | runs-on: ubuntu-latest 56 | permissions: 57 | checks: write 58 | pull-requests: write 59 | if: always() 60 | 61 | steps: 62 | - name: Download Artifacts 63 | uses: actions/download-artifact@v4.1.7 64 | with: 65 | path: artifacts 66 | 67 | - name: Publish Test Results 68 | uses: EnricoMi/publish-unit-test-result-action@v1 69 | with: 70 | files: "artifacts/**/*.xml" 71 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .classpath 3 | .vscode 4 | .settings 5 | .project 6 | target 7 | bin 8 | repl-port 9 | *.iml 10 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: java 4 | 5 | cache: 6 | directories: 7 | - $HOME/protobuf 8 | - $home/.m2 9 | 10 | before_install: 11 | - ./scripts/install-protobuf.sh 12 | - export PATH=$PATH:$HOME/bin 13 | -------------------------------------------------------------------------------- /CHANGES.markdown: -------------------------------------------------------------------------------- 1 | 0.5.1 2 | ===== 3 | 4 | * Upgraded dropwizard metrics to 4.0.2 5 | 6 | 0.5.0 7 | ===== 8 | 9 | * Upgrade to Netty 4 10 | 11 | NOTES: 12 | 13 | - See [Netty 4](http://netty.io/wiki/new-and-noteworthy-in-4.0.html) upgrade notes 14 | 15 | 16 | - As [mentioned](http://netty.io/wiki/new-and-noteworthy-in-4.0.html#write-does-not-flush-automatically) 17 | Netty 4 won't flush sends automatically. By default this client will flush after each message, 18 | this can be controlled via the `autoFlush` field on the Transports. 19 | 20 | - `Resolver` class was removed. Netty will resolve on each new channel when the 21 | supplied address is created via `InetSocketAddress.createUnresolved`. 22 | 23 | - use of `HashedWheelTimer` has been replaced by the Executor provided by the 24 | `ChannelHandlerContext` 25 | 26 | - Previous versions used to explicitly disable SSL TLS renegotiation. This no longer appears to be part of Netty. 27 | [Stack Overflow](https://stackoverflow.com/questions/31418644/is-it-possible-to-disable-tls-renegotiation-in-netty-4) 28 | suggests setting the JDK8+ System Property `jdk.tls.rejectClientInitiatedRenegotiation` 29 | 30 | - Use individual Netty Jars rather than netty-all. Use of netty-all can hinder 31 | dependency resolution when different projects use Netty on the same classpath. 32 | See example from [Aleph](https://github.com/ztellman/aleph/issues/335) 33 | 34 | - Remove `ChannelGroupHandler` the Transport connect add the channel at each connection attempt. 35 | The Netty `Channels` class automatically removes channels as they close. 36 | 37 | * Enable metrics reports to include state values based on the values of 38 | the metrics. This adds support for computation and reporting of metric 39 | states. The implementation associates ranges of values with user-defined 40 | states, defaulting to `ok` if no filters have been set up or none of the 41 | associated filters applies. 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # Getting started 2 | 3 | This project encompasses: 4 | 5 | 1. A Java client for the Riemann protocol 6 | 2. The Riemann protocol buffer definition, and 7 | 3. Its corresponding (auto-generated) Java classes 8 | 9 | [![Clojars 10 | Project](https://img.shields.io/clojars/v/io.riemann/riemann-java-client.svg)](https://clojars.org/io.riemann/riemann-java-client) 11 | 12 | [![Riemann Clojure Client testing](https://github.com/riemann/riemann-java-client/actions/workflows/test.yml/badge.svg)](https://github.com/riemann/riemann-java-client/actions/workflows/test.yml) 13 | 14 | ## Artifacts 15 | 16 | Artifacts are available through 17 | [clojars](https://clojars.org/io.riemann/riemann-java-client) which you can add 18 | to your maven repository like so: 19 | 20 | ```xml 21 | 22 | clojars.org 23 | http://clojars.org/repo 24 | 25 | ``` 26 | 27 | **Note:** 28 | The namespace for the client was previously `com.aphyr` but has been 29 | renamed to `io.riemann` since the 0.4.2 release. You need to update your 30 | dependencies. 31 | 32 | ## Example 33 | 34 | ``` java 35 | RiemannClient c = RiemannClient.tcp("my.riemann.server", 5555); 36 | c.connect(); 37 | c.event(). 38 | service("fridge"). 39 | state("running"). 40 | metric(5.3). 41 | tags("appliance", "cold"). 42 | send(). 43 | deref(5000, java.util.concurrent.TimeUnit.MILLISECONDS); 44 | 45 | c.query("tagged \"cold\" and metric > 0").deref(); // => List; 46 | c.close(); 47 | ``` 48 | 49 | Clients will automatically attempt to reconnect every 5 seconds. Writes 50 | will fail instantaneously when no connection is available. 51 | 52 | `.send()` proceeds asynchronously and returns as soon as Netty flushes 53 | the write possible. `.send()` returns a 54 | `io.riemann.riemann.client.IPromise` containing the response from the 55 | write (which also supports Clojure's Deref protocol). If you do not 56 | deref this promise, the client makes *no* guarantees about event 57 | delivery: it will, for example, discard writes when there are too many 58 | messages outstanding on the wire, when Riemann cannot keep up with load, 59 | and so on. You *should* deref sends at some point, if for no other 60 | reason than to handle backpressure. 61 | 62 | Calling `.deref()` will throw a ServerError if the server responds with 63 | an error, or other Runtime/IOExceptions for error conditions, like a 64 | channel being disconnected, etc. 65 | 66 | ```java 67 | try { 68 | if (!c.event(). 69 | service("fridge"). 70 | state("running"). 71 | send(). 72 | deref(1, java.util.concurrent.TimeUnit.SECONDS)) { 73 | throw new IOException("Timed out.") 74 | } 75 | } catch (Exception e) { 76 | retry(); 77 | } 78 | ``` 79 | 80 | This code blocks for 1 second before retrying, returns a Message if the 81 | write succeeded, null if the promise is still outstanding, and throws if 82 | a failure is known to have occurred. This means you can send multiple 83 | copies of an event if latencies exceed 1000 ms. There is no reliable way 84 | to distinguish between failure and delay in an asynchronous network, so 85 | think ahead. `.deref()` blocks indefinitely, but will return as soon as 86 | the Netty connection fails, so it may be the safest option when 87 | arbitrary delays are acceptable. 88 | 89 | Each client allows thousands of outstanding concurrent requests at any 90 | time, so a small number of threads can efficiently pipeline many 91 | operations over the same client. I suggest performing writes on a 92 | special monitoring thread or threads, and pushing the response futures 93 | onto a threadpoolexecutor for `deref`ing. 94 | 95 | For higher performance (by orders of magnitude) you can also send 96 | multiple events batched in a single message. Use 97 | `RiemannClient.sendEvents(...)` to send multiple events at once. 98 | 99 | To automatically batch events, wrap any IRiemannClient in a 100 | RiemannBatchClient, which automatically bundles events together into 101 | messages for you. 102 | 103 | # Release and deployment 104 | 105 | 1. Increment the version. 106 | 107 | 2. Compile the client. 108 | 109 | ```sh 110 | mvn compile -rf :riemann-java-client 111 | ``` 112 | 113 | 3. Deploy the client. 114 | 115 | ```sh 116 | mvn deploy -tf :riemann-java-client-parent 117 | mvn deploy -rf :riemann-java-client 118 | ``` 119 | 120 | # Hacking 121 | 122 | You'll need [protobuf 3.16.1](https://github.com/google/protobuf) - you 123 | can just install the `protoc` binary. After that, `mvn package` should 124 | build a JAR, and `mvn install` will drop it in your local repository. 125 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Riemann Security and Disclosure Information 2 | This page describes Riemann security and disclosure information. 3 | 4 | ## Supported Versions 5 | 6 | The currently supported version of Riemann for security-patching purposes is always the latest version. 7 | 8 | ## Security Announcements 9 | 10 | Will be made on the [Riemann mailing list](https://groups.google.com/g/riemann-users?pli=1). 11 | 12 | ## Report a Vulnerability 13 | 14 | We're extremely grateful for security researchers and users that report vulnerabilities to Riemann. All reports are thoroughly investigated by the maintainers. 15 | 16 | To make a report, you should email the private security@riemann.io list with the details. 17 | 18 | ## When Should I Report a Vulnerability? 19 | 20 | * You think you discovered a potential security vulnerability in Riemann. 21 | * You are unsure how a vulnerability affects Riemann. 22 | * You think you discovered a vulnerability in another project that Riemann depends on 23 | 24 | For projects with their own vulnerability reporting and disclosure process, please report it directly there. 25 | 26 | ## When Should I NOT Report a Vulnerability? 27 | 28 | * You need help tuning Riemann components for security 29 | * You need help applying security related updates 30 | * Your issue is not security related 31 | 32 | ## Security Vulnerability Response 33 | 34 | Each report is acknowledged and analyzed within 5 working days. 35 | 36 | Any vulnerability information shared stays within Riemann project and will not be disseminated to other projects unless it is necessary to get the issue fixed. 37 | 38 | As the security issue moves from triage, to identified fix, to release planning we will keep the reporter updated. 39 | 40 | ## Public Disclosure Timing 41 | 42 | A public disclosure date is negotiated by the Riemann maintainers nd the bug submitter. We prefer to fully disclose the bug as soon as possible once a user mitigation is available. It is reasonable to delay disclosure when the bug or the fix is not yet fully understood, the solution is not well-tested, or for vendor coordination. The timeframe for disclosure is from immediate (especially if it's already publicly known) to a few weeks. For a vulnerability with a straightforward mitigation, we expect report date to disclosure date to be on the order of 7 days. The Riemann maintainers hold the final say when setting a disclosure date. 43 | -------------------------------------------------------------------------------- /metrics2-riemann-reporter/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 4.0.0 7 | metrics2-riemann-reporter 8 | 9 | riemann-java-client-parent 10 | io.riemann 11 | 0.5.4-SNAPSHOT 12 | 13 | 14 | 15 | 16 | 17 | com.yammer.metrics 18 | metrics-core 19 | 2.1.2 20 | 21 | 22 | ${project.groupId} 23 | riemann-java-client 24 | ${project.version} 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /metrics2-riemann-reporter/src/main/java/com/yammer/metrics/reporting/RiemannReporter.java: -------------------------------------------------------------------------------- 1 | package com.yammer.metrics.reporting; 2 | 3 | import io.riemann.riemann.client.EventDSL; 4 | import io.riemann.riemann.client.IRiemannClient; 5 | import io.riemann.riemann.client.RiemannClient; 6 | import com.yammer.metrics.Metrics; 7 | import com.yammer.metrics.core.*; 8 | import com.yammer.metrics.stats.Snapshot; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | import java.io.IOException; 13 | import java.net.InetSocketAddress; 14 | import java.util.ArrayList; 15 | import java.util.Arrays; 16 | import java.util.Collection; 17 | import java.util.List; 18 | import java.util.Map.Entry; 19 | import java.util.SortedMap; 20 | import java.util.concurrent.TimeUnit; 21 | 22 | public class RiemannReporter extends AbstractPollingReporter implements MetricProcessor { 23 | private static final Logger LOG = LoggerFactory.getLogger(RiemannReporter.class); 24 | public final IRiemannClient riemann; 25 | public final Config c; 26 | 27 | public static class Config { 28 | 29 | public final MetricsRegistry metricsRegistry; 30 | public final MetricPredicate predicate; 31 | public final boolean printVMMetrics; 32 | public final String host; 33 | public final int port; 34 | public final long period; 35 | public final TimeUnit unit; 36 | public final String prefix; 37 | public final String separator; 38 | public final VirtualMachineMetrics vm = VirtualMachineMetrics.getInstance(); 39 | public final Clock clock; 40 | public final String name; 41 | public final String localHost; 42 | public final List tags; 43 | 44 | private Config(MetricsRegistry metricsRegistry, MetricPredicate predicate, boolean printVMMetrics, 45 | String host, int port, long period, TimeUnit unit, String prefix, String separator, 46 | Clock clock, String name, String localHost, List tags) { 47 | this.metricsRegistry = metricsRegistry; 48 | this.predicate = predicate; 49 | this.printVMMetrics = printVMMetrics; 50 | this.host = host; 51 | this.port = port; 52 | this.period = period; 53 | this.unit = unit; 54 | this.prefix = prefix; 55 | this.separator = separator; 56 | this.clock = clock; 57 | this.name = name; 58 | this.localHost = localHost; 59 | this.tags = tags; 60 | 61 | } 62 | 63 | public static ConfigBuilder newBuilder() { 64 | return new ConfigBuilder(); 65 | } 66 | 67 | @Override 68 | public String toString() { 69 | return "Config{" + 70 | "print_metrics:" + printVMMetrics + ", host:" + host + ", port:" + port + ", period:" + period + 71 | ", unit:" + unit + ", prefix:" + prefix + ", separator:" + separator + ", clock:" + clock + 72 | ", name:" + name + ", localhost:" + localHost + ", metrics_registry:" + metricsRegistry + 73 | ", predicate:" + predicate + ", tags:" + Arrays.toString(tags.toArray()) + "}"; 74 | } 75 | } 76 | 77 | public static final class ConfigBuilder { 78 | // Default values for Config 79 | private MetricsRegistry metricsRegistry = Metrics.defaultRegistry(); 80 | private MetricPredicate predicate = MetricPredicate.ALL; 81 | private boolean printVMMetrics = true; 82 | private String host = "localhost"; 83 | private int port = 5555; 84 | private long period = 60; 85 | private TimeUnit unit = TimeUnit.SECONDS; 86 | private String prefix = null; 87 | private String separator = " "; 88 | private final VirtualMachineMetrics vm = VirtualMachineMetrics.getInstance(); 89 | private Clock clock = Clock.defaultClock(); 90 | private String name = "riemann-reporter"; 91 | private String localHost = null; 92 | private List tags = new ArrayList(); 93 | 94 | private ConfigBuilder() { } 95 | 96 | public Config build(){ 97 | return new Config(metricsRegistry, predicate, printVMMetrics, host, port, 98 | period, unit, prefix, separator, clock, name, localHost, tags); 99 | } 100 | 101 | public ConfigBuilder metricsRegistry(MetricsRegistry r) { metricsRegistry = r; return this; } 102 | public ConfigBuilder predicate(MetricPredicate p) { predicate = p; return this; } 103 | public ConfigBuilder printVMMetrics(Boolean p) { printVMMetrics = p; return this; } 104 | public ConfigBuilder host(String h) { host = h; return this; } 105 | public ConfigBuilder port(int p) { port = p; return this; } 106 | public ConfigBuilder period(long p) { period = p; return this; } 107 | public ConfigBuilder unit(TimeUnit t) { unit = t; return this; } 108 | public ConfigBuilder prefix(String p) { prefix = p; return this; } 109 | public ConfigBuilder separator(String s) { separator = s; return this; } 110 | public ConfigBuilder clock(Clock c) { clock = c; return this; } 111 | public ConfigBuilder name(String n) { name = n; return this; } 112 | public ConfigBuilder localHost(String l) { localHost = l; return this; } 113 | public ConfigBuilder tags(Collection ts) { tags.clear(); tags.addAll(ts); return this; } 114 | } 115 | 116 | public static void enable(final Config config) { 117 | try { 118 | if (config == null) 119 | throw new IllegalArgumentException("Config cannot be null"); 120 | 121 | final RiemannReporter reporter = new RiemannReporter(config); 122 | reporter.start(config.period, config.unit); 123 | } catch (Exception e) { 124 | LOG.error("Error creating/starting Riemann reporter: ", e); 125 | } 126 | } 127 | 128 | public RiemannReporter(final Config c) throws IOException { 129 | this(c, RiemannClient.tcp(new InetSocketAddress(c.host, c.port))); 130 | riemann.connect(); 131 | } 132 | 133 | public RiemannReporter(final Config c, final IRiemannClient riemann) { 134 | super(c.metricsRegistry, c.name); 135 | this.riemann = riemann; 136 | this.c = c; 137 | } 138 | 139 | @Override 140 | public void run() { 141 | try { 142 | final long epoch = c.clock.time() / 1000; 143 | if (c.printVMMetrics) { 144 | sendVMMetrics(epoch); 145 | } 146 | 147 | sendRegularMetrics(epoch); 148 | } catch (Exception e) { 149 | LOG.warn("Error writing to Riemann", e); 150 | } 151 | } 152 | 153 | protected void sendRegularMetrics(final Long epoch) { 154 | for (Entry> entry : getMetricsRegistry().groupedMetrics(c.predicate).entrySet()) { 155 | for (Entry subEntry : entry.getValue().entrySet()) { 156 | final Metric metric = subEntry.getValue(); 157 | if (metric != null) { 158 | try { 159 | metric.processWith(this, subEntry.getKey(), epoch); 160 | } catch (Exception ignored) { 161 | LOG.error("Error sending regular metric:", ignored); 162 | } 163 | } 164 | } 165 | } 166 | } 167 | 168 | private EventDSL newEvent() { 169 | EventDSL event = riemann.event(); 170 | if (c.localHost != null) { 171 | event.host(c.localHost); 172 | } 173 | if (!c.tags.isEmpty()) { 174 | event.tags(c.tags); 175 | } 176 | return event; 177 | } 178 | 179 | // The service name for a given metric. 180 | public String service(MetricName name, String... rest) { 181 | final StringBuilder sb = new StringBuilder(); 182 | if (null != c.prefix) { 183 | sb.append(c.prefix).append(c.separator); 184 | } 185 | sb.append(name.getGroup()) 186 | .append(c.separator) 187 | .append(name.getType()) 188 | .append(c.separator); 189 | if (name.hasScope()) { 190 | sb.append(name.getScope()) 191 | .append(c.separator); 192 | } 193 | sb.append(name.getName()); 194 | for (String part : rest) { 195 | sb.append(c.separator); 196 | sb.append(part); 197 | } 198 | return sb.toString(); 199 | } 200 | 201 | public String service(String... parts) { 202 | final StringBuilder sb = new StringBuilder(); 203 | if (null != c.prefix) { 204 | sb.append(c.prefix).append(c.separator); 205 | } 206 | 207 | for (String p : parts) { 208 | sb.append(p).append(c.separator); 209 | } 210 | 211 | return sb.substring(0, sb.length() - c.separator.length()); 212 | } 213 | 214 | @Override 215 | public void processGauge(MetricName name, Gauge gauge, Long epoch) { 216 | Object v = gauge.value(); 217 | EventDSL e = newEvent().service(service(name)).time(epoch); 218 | if (v instanceof Integer) { 219 | e.metric((Integer) v).send(); 220 | } else if (v instanceof Long) { 221 | e.metric((Long) v).send(); 222 | } else if (v instanceof Double) { 223 | e.metric((Double) v).send(); 224 | } else if (v instanceof Float) { 225 | e.metric((Float) v).send(); 226 | } else if (v instanceof Number) { 227 | e.metric(((Number) v).floatValue()).send(); 228 | } 229 | } 230 | 231 | @Override 232 | public void processCounter(MetricName name, Counter counter, Long epoch) { 233 | newEvent() 234 | .service(service(name)) 235 | .metric(counter.count()) 236 | .time(epoch) 237 | .send(); 238 | } 239 | 240 | @Override 241 | public void processMeter(MetricName name, Metered meter, Long epoch) { 242 | newEvent() 243 | .service(service(name)) 244 | .metric(meter.oneMinuteRate()) 245 | .time(epoch) 246 | .send(); 247 | } 248 | 249 | @Override 250 | public void processHistogram(MetricName name, Histogram histogram, Long epoch) throws IOException { 251 | final String service = service(name); 252 | sendSummary(name, histogram, epoch); 253 | sendSample(name, histogram, epoch); 254 | } 255 | 256 | @Override 257 | public void processTimer(MetricName name, Timer timer, Long epoch) { 258 | processMeter(name, timer, epoch); 259 | sendSummary(name, timer, epoch); 260 | sendSample(name, timer, epoch); 261 | } 262 | 263 | protected void sendVMMetrics(long epoch) { 264 | newEvent().time(epoch).service(service("jvm", "memory", "heap-usage")).metric(c.vm.heapUsage()).send(); 265 | newEvent().time(epoch).service(service("jvm", "memory", "non-heap-usage")).metric(c.vm.nonHeapUsage()).send(); 266 | for (Entry pool : c.vm.memoryPoolUsage().entrySet()) { 267 | newEvent().time(epoch).service(service("jvm", "memory", "pool-usage", pool.getKey())).metric(pool.getValue()).send(); 268 | } 269 | newEvent().time(epoch).service(service("jvm", "thread", "daemon-count")).metric(c.vm.daemonThreadCount()).send(); 270 | newEvent().time(epoch).service(service("jvm", "thread", "count")).metric(c.vm.threadCount()).send(); 271 | newEvent().time(epoch).service(service("jvm", "uptime")).metric(c.vm.uptime()).send(); 272 | newEvent().time(epoch).service(service("jvm", "fd-usage")).metric(c.vm.fileDescriptorUsage()).send(); 273 | 274 | for(Entry entry : c.vm.threadStatePercentages().entrySet()) { 275 | newEvent().time(epoch).service(service("jvm", "thread", "state", entry.getKey().toString().toLowerCase())).metric(entry.getValue()).send(); 276 | } 277 | 278 | for(Entry entry : c.vm.garbageCollectors().entrySet()) { 279 | newEvent().time(epoch).service(service("jvm", "gc", entry.getKey(), "time")).metric(entry.getValue().getTime(TimeUnit.MILLISECONDS)).send(); 280 | newEvent().time(epoch).service(service("jvm", "gc", entry.getKey(), "runs")).metric(entry.getValue().getRuns()).send(); 281 | } 282 | } 283 | 284 | public void sendSummary(MetricName name, Summarizable metric, Long epoch) { 285 | newEvent().time(epoch).service(service(name, "min")).metric(metric.min()).send(); 286 | newEvent().time(epoch).service(service(name, "max")).metric(metric.max()).send(); 287 | newEvent().time(epoch).service(service(name, "mean")).metric(metric.mean()).send(); 288 | newEvent().time(epoch).service(service(name, "stddev")).metric(metric.stdDev()).send(); 289 | } 290 | 291 | public void sendSample(MetricName name, Sampling metric, Long epoch) { 292 | final Snapshot s = metric.getSnapshot(); 293 | newEvent().time(epoch).service(service(name, ".5")).metric(s.getMedian()).send(); 294 | newEvent().time(epoch).service(service(name, ".75")).metric(s.get75thPercentile()).send(); 295 | newEvent().time(epoch).service(service(name, ".95")).metric(s.get95thPercentile()).send(); 296 | newEvent().time(epoch).service(service(name, ".98")).metric(s.get98thPercentile()).send(); 297 | newEvent().time(epoch).service(service(name, ".99")).metric(s.get99thPercentile()).send(); 298 | newEvent().time(epoch).service(service(name, ".999")).metric(s.get999thPercentile()).send(); 299 | } 300 | } 301 | -------------------------------------------------------------------------------- /metrics2-riemann-reporter/src/test/java/riemann/java/client/tests/RiemannReporterConfigTest.java: -------------------------------------------------------------------------------- 1 | package riemann.java.client.tests; 2 | 3 | import com.yammer.metrics.reporting.RiemannReporter; 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | 7 | import java.util.HashSet; 8 | import java.util.concurrent.TimeUnit; 9 | 10 | import static org.junit.Assert.assertEquals; 11 | import static org.junit.Assert.assertTrue; 12 | 13 | public class RiemannReporterConfigTest { 14 | 15 | private RiemannReporter.ConfigBuilder builder; 16 | 17 | @Before 18 | public void setUp() { 19 | builder = RiemannReporter.Config.newBuilder(); 20 | } 21 | 22 | @Test 23 | public void testDefaultConfigBuilding() { 24 | RiemannReporter.Config c = builder.build(); 25 | assertEquals(c.port, 5555); 26 | assertEquals(c.unit, TimeUnit.SECONDS); 27 | assertEquals(c.separator, " "); 28 | assertEquals(c.host, "localhost"); 29 | assertTrue(c.tags.isEmpty()); 30 | } 31 | 32 | @Test 33 | public void testConfigBuilding() { 34 | RiemannReporter.Config c = builder.host("127.0.0.1") 35 | .unit(TimeUnit.MILLISECONDS) 36 | .port(9999) 37 | .separator("#") 38 | .host("riemann") 39 | .build(); 40 | 41 | assertEquals(c.port, 9999); 42 | assertEquals(c.unit, TimeUnit.MILLISECONDS); 43 | assertEquals(c.separator, "#"); 44 | assertEquals(c.host, "riemann"); 45 | 46 | } 47 | 48 | @Test 49 | public void testTagsConfigBuilding() { 50 | RiemannReporter.Config c = builder.tags(new HashSet(){{ add("abc"); add("123"); }}) 51 | .build(); 52 | assertTrue(c.tags.contains("abc")); 53 | assertTrue(c.tags.contains("123")); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /metrics3-riemann-reporter/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 4.0.0 7 | metrics3-riemann-reporter 8 | 9 | riemann-java-client-parent 10 | io.riemann 11 | 0.5.4-SNAPSHOT 12 | 13 | 14 | 15 | 16 | io.dropwizard.metrics 17 | metrics-core 18 | 3.2.6 19 | 20 | 21 | ${project.groupId} 22 | riemann-java-client 23 | ${project.version} 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /metrics3-riemann-reporter/src/main/java/com/codahale/metrics/riemann/Riemann.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Dominic LoBue 3 | * Copyright 2014 Brandon Seibel 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package com.codahale.metrics.riemann; 19 | 20 | import io.riemann.riemann.client.IRiemannClient; 21 | import io.riemann.riemann.client.RiemannClient; 22 | import io.riemann.riemann.client.RiemannBatchClient; 23 | import io.riemann.riemann.client.UnsupportedJVMException; 24 | 25 | 26 | import java.io.Closeable; 27 | import java.io.IOException; 28 | 29 | 30 | public class Riemann implements Closeable { 31 | 32 | IRiemannClient client; 33 | 34 | public Riemann(String host, Integer port) throws IOException { 35 | this(host, port, 10); 36 | } 37 | 38 | public Riemann(String host, Integer port, int batchSize) throws IOException { 39 | this(getClient(host, port, batchSize)); 40 | } 41 | 42 | private static IRiemannClient getClient(String host, Integer port, int batchSize) throws IOException { 43 | IRiemannClient c = RiemannClient.tcp(host, port); 44 | try { 45 | return new RiemannBatchClient(c, batchSize); 46 | } catch (UnsupportedJVMException e) { 47 | return c; 48 | } 49 | } 50 | 51 | public Riemann(IRiemannClient client) { 52 | this.client = client; 53 | } 54 | 55 | public void connect() throws IOException { 56 | if (!client.isConnected()) { 57 | client.connect(); 58 | } 59 | } 60 | 61 | @Override 62 | public void close() throws IOException { 63 | if (client != null) { 64 | client.close(); 65 | } 66 | 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /metrics3-riemann-reporter/src/main/java/com/codahale/metrics/riemann/ValueFilter.java: -------------------------------------------------------------------------------- 1 | package com.codahale.metrics.riemann; 2 | 3 | /** 4 | * Filters values to determine if they fall into a given range via the 5 | * {@link #applies(double)} method. Ranges are intervals, with endpoint 6 | * inclusion configurable. The {@code state} property implicitly binds values in 7 | * the represented range to that state for reporting. 8 | *

9 | * Instances are created using {@link ValueFilter.Builder}. Endpoints not 10 | * specified default to positive or negative infinity, respectively. For 11 | * example, {@code new ValueFilter.Builder("critical").withLower(50).build())} 12 | * creates a ValueFilter instance that binds "critical" to all values greater 13 | * than or equal to 50 and {@code new ValueFilter.Builder("warn") 14 | * .withUpperExclusive(300) 15 | * .withLower(100).build())} creates an instance 16 | * binding "warn" to values greater than or equal to 100 and strictly less than 17 | * 300. 18 | */ 19 | public class ValueFilter { 20 | 21 | /** Lower bound of the interval */ 22 | private final double lower; 23 | 24 | /** Upper bound of the interval */ 25 | private final double upper; 26 | 27 | /** Whether or not the upper bound is included */ 28 | private final boolean upperIncluded; 29 | 30 | /** Whether or not the lower bound is included */ 31 | private final boolean lowerIncluded; 32 | 33 | /** State bound to values in this interval */ 34 | private final String state; 35 | 36 | /** Create a ValueFilter instance using the given builder */ 37 | private ValueFilter(Builder builder) { 38 | this.lower = builder.lower; 39 | this.upper = builder.upper; 40 | this.upperIncluded = builder.upperIncluded; 41 | this.lowerIncluded = builder.lowerIncluded; 42 | this.state = builder.state; 43 | } 44 | 45 | /** 46 | * Determine whether or not the given value falls within the range 47 | * associated with this ValueFilter. 48 | * 49 | * @param value value to test 50 | * @return true iff value is within the target interval 51 | */ 52 | public boolean applies(double value) { 53 | boolean ret = upperIncluded ? value <= upper : value < upper; 54 | return ret && (lowerIncluded ? lower <= value : lower < value); 55 | } 56 | 57 | /** 58 | * @return the state bound to values in this ValueFilter 59 | */ 60 | public String getState() { 61 | return state; 62 | } 63 | 64 | /** 65 | * Builder for ValueFilter instances 66 | */ 67 | public static class Builder { 68 | 69 | /** Lower bound for the interval */ 70 | private double lower = Double.NEGATIVE_INFINITY; 71 | 72 | /** Upper bound for the interval */ 73 | private double upper = Double.POSITIVE_INFINITY; 74 | 75 | /** Whether or not the upper bound is included in the interval */ 76 | private boolean upperIncluded = true; 77 | 78 | /** Whether or not the lower bound is included in the interval */ 79 | private boolean lowerIncluded = true; 80 | 81 | /** State bound to values in the interval */ 82 | private final String state; 83 | 84 | /** 85 | * Create a new builder for ValueFilters bound to the given state 86 | * 87 | * @param state state bound to values in the interval 88 | */ 89 | public Builder(String state) { 90 | this.state = state; 91 | } 92 | 93 | /** 94 | * @param lower inclusive lower bound for the interval 95 | * @return Builder instance with lower set inclusively (i.e. lower bound 96 | * is included in the interval) 97 | */ 98 | public Builder withLower(double lower) { 99 | this.lower = lower; 100 | return this; 101 | } 102 | 103 | /** 104 | * @param upper inclusive upper bound for the interval 105 | * @return Builder instance with upper set inclusively (i.e. upper bound 106 | * is included in the interval) 107 | */ 108 | public Builder withUpper(double upper) { 109 | this.upper = upper; 110 | return this; 111 | } 112 | 113 | /** 114 | * @param lower exclusive lower bound for the interval 115 | * @return Builder instance with lower set exclusively (i.e. lower bound 116 | * is not included in the interval) 117 | */ 118 | public Builder withLowerExclusive(double lower) { 119 | this.lower = lower; 120 | this.lowerIncluded = false; 121 | return this; 122 | }; 123 | 124 | /** 125 | * @param upper exclusive upper bound for the interval 126 | * @return Builder instance with upper set exclusively (i.e. upper bound 127 | * is not included in the interval) 128 | */ 129 | public Builder withUpperExclusive(double upper) { 130 | this.upper = upper; 131 | this.upperIncluded = false; 132 | return this; 133 | } 134 | 135 | /** 136 | * Build a ValueFilter using this Builder instance 137 | * 138 | * @return ValueFilter with properties determined by those of this 139 | * Builder 140 | */ 141 | public ValueFilter build() { 142 | return new ValueFilter(this); 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /metrics3-riemann-reporter/src/main/java/com/codahale/metrics/riemann/ValueFilterMap.java: -------------------------------------------------------------------------------- 1 | package com.codahale.metrics.riemann; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | import java.util.Map.Entry; 8 | import java.util.concurrent.ConcurrentHashMap; 9 | import java.util.concurrent.TimeUnit; 10 | 11 | import com.codahale.metrics.Counter; 12 | import com.codahale.metrics.Histogram; 13 | import com.codahale.metrics.Metered; 14 | import com.codahale.metrics.Metric; 15 | import com.codahale.metrics.Snapshot; 16 | import com.codahale.metrics.Timer; 17 | 18 | /** 19 | * Enables {@link ValueFilter}s to be associated with measures reported by 20 | * Metrics so that state can be computed based on values at report-generation 21 | * time. The {@link #addFilter(Metric, String, ValueFilter)} method associates a 22 | * {@link ValueFilter} with a measure and the {@code state} methods compute what 23 | * state should be reported based on the value of a measure. 24 | *

25 | * For example, {@code 26 | * ValueFilterMap valueFilterMap = new ValueFilterMap(); 27 | * valueFilterMap 28 | .addFilter(timer, ValueFilterMap.MAX, 29 | new ValueFilter.Builder("critical").withLower(50).build()) 30 | .addFilter(timer, ValueFilterMap.MEAN, new ValueFilter.Builder("warn") 31 | .withUpperExclusive(200).withLower(100).build()); 32 | * } Attaches filters to the mean and max values reported by timers so that 33 | * {@code state(timer, "max")} will return "critical" if the max value reported 34 | * by the timer is greater than or equal to 50 and {@code state(timer, "mean")} 35 | * will return "warn" if the mean value is between 100 (inclusive) and 200 36 | * (exclusive). Filters are applied in the order they are added and the last one 37 | * that applies wins. If no filter applies, state methods return "ok". 38 | */ 39 | public class ValueFilterMap { 40 | 41 | // Names for values reported by metrics - call these "measures" 42 | public static final String MAX = "max"; 43 | 44 | public static final String MEAN = "mean"; 45 | 46 | public static final String MIN = "min"; 47 | 48 | public static final String STDDEV = "stddev"; 49 | 50 | public static final String P50 = "p50"; 51 | 52 | public static final String P75 = "p75"; 53 | 54 | public static final String P95 = "p95"; 55 | 56 | public static final String P98 = "p98"; 57 | 58 | public static final String P99 = "p99"; 59 | 60 | public static final String P999 = "p999"; 61 | 62 | public static final String COUNT = "count"; 63 | 64 | public static final String M1_RATE = "m1_rate"; 65 | 66 | public static final String M5_RATE = "m5_rate"; 67 | 68 | public static final String M15_RATE = "m15_rate"; 69 | 70 | public static final String MEAN_RATE = "mean_rate"; 71 | 72 | // TODO: when Java 8 is available, replace this bloated setup with lambdas 73 | // 74 | /** Implementations extract specific values from snapshots */ 75 | interface SnapValue { 76 | 77 | /** @return the snapshot value that this lamba-like thing is keyed on */ 78 | double value(Snapshot snapshot); 79 | } 80 | 81 | /** Implementations extract specific statistics from metered impls */ 82 | interface MeteredValue { 83 | 84 | /** @return the metered value that this lamba-like thing is keyed on */ 85 | double value(Metered metered); 86 | } 87 | 88 | /** 89 | * Map with keys equal to snapshot statistics names and values lambda-like 90 | * objects that extract the named statistic - statically initialized 91 | */ 92 | private static final Map snapValueMap = new HashMap(); 93 | 94 | /** 95 | * Map with keys equal to metered value names and values lambda-like objects 96 | * that extract the named metrics - statically initialized 97 | */ 98 | private static final Map meteredValueMap = new HashMap(); 99 | 100 | /** 101 | * Loads lambda-like functions into maps used to extract values from metrics 102 | */ 103 | static { 104 | snapValueMap.put(MAX, new SnapValue() { 105 | 106 | public double value(Snapshot snapshot) { 107 | return snapshot.getMax(); 108 | } 109 | }); 110 | snapValueMap.put(MEAN, new SnapValue() { 111 | 112 | public double value(Snapshot snapshot) { 113 | return snapshot.getMean(); 114 | } 115 | }); 116 | snapValueMap.put(MIN, new SnapValue() { 117 | 118 | public double value(Snapshot snapshot) { 119 | return snapshot.getMin(); 120 | } 121 | }); 122 | snapValueMap.put(STDDEV, new SnapValue() { 123 | 124 | public double value(Snapshot snapshot) { 125 | return snapshot.getStdDev(); 126 | } 127 | }); 128 | snapValueMap.put(P50, new SnapValue() { 129 | 130 | public double value(Snapshot snapshot) { 131 | return snapshot.getMedian(); 132 | } 133 | }); 134 | snapValueMap.put(P75, new SnapValue() { 135 | 136 | public double value(Snapshot snapshot) { 137 | return snapshot.get75thPercentile(); 138 | } 139 | }); 140 | snapValueMap.put(P95, new SnapValue() { 141 | 142 | public double value(Snapshot snapshot) { 143 | return snapshot.get95thPercentile(); 144 | } 145 | }); 146 | snapValueMap.put(P98, new SnapValue() { 147 | 148 | public double value(Snapshot snapshot) { 149 | return snapshot.get98thPercentile(); 150 | } 151 | }); 152 | snapValueMap.put(P99, new SnapValue() { 153 | 154 | public double value(Snapshot snapshot) { 155 | return snapshot.get99thPercentile(); 156 | } 157 | }); 158 | snapValueMap.put(P999, new SnapValue() { 159 | 160 | public double value(Snapshot snapshot) { 161 | return snapshot.get999thPercentile(); 162 | } 163 | }); 164 | 165 | meteredValueMap.put(COUNT, new MeteredValue() { 166 | 167 | public double value(Metered metered) { 168 | return metered.getCount(); 169 | } 170 | }); 171 | meteredValueMap.put(M1_RATE, new MeteredValue() { 172 | 173 | public double value(Metered metered) { 174 | return metered.getOneMinuteRate(); 175 | } 176 | }); 177 | meteredValueMap.put(M5_RATE, new MeteredValue() { 178 | 179 | public double value(Metered metered) { 180 | return metered.getFiveMinuteRate(); 181 | } 182 | }); 183 | meteredValueMap.put(M15_RATE, new MeteredValue() { 184 | 185 | public double value(Metered metered) { 186 | return metered.getFifteenMinuteRate(); 187 | } 188 | }); 189 | meteredValueMap.put(MEAN_RATE, new MeteredValue() { 190 | 191 | public double value(Metered metered) { 192 | return metered.getMeanRate(); 193 | } 194 | }); 195 | } 196 | 197 | /** 198 | * If m is a Metric, then filterMapMap.get(m) is a map with keys equal to 199 | * measures (constants above) and values equal to ValueFilter instances. 200 | * ValueFilters mapped to measures are applied to determine the state 201 | * associated with the given measure reported to Riemann in m's report. For 202 | * example, if t is a timer, filterMapMap(t) is a map of lists of filters. 203 | * The keys to that map are "max", "mean", "min", etc. - all of the measures 204 | * that t has getters for. The associated value is a list of filters. The 205 | * last filter that applies determines what the reported state is for the 206 | * measure. 207 | */ 208 | private final Map>> filterMapMap = new ConcurrentHashMap>>(); 209 | 210 | public ValueFilterMap() { 211 | super(); 212 | } 213 | 214 | /** 215 | * Add a filter associated with the given measure for the given metric. 216 | * 217 | * @param metric metric instance reporting the measure that the filter 218 | * applies to 219 | * @param measure name of the value reported by the metric that the filter 220 | * applies to 221 | * @param filter ValueFilter instance that, if it applies, may determine the 222 | * state reported for the measure 223 | * @return a reference to *this (for fluent activation) 224 | */ 225 | public ValueFilterMap addFilter(Metric metric, String measure, 226 | ValueFilter filter) { 227 | Map> filterMap = filterMapMap.get(metric); 228 | List filterList; 229 | if (filterMap == null) { 230 | filterMap = new HashMap>(); 231 | filterMapMap.put(metric, filterMap); 232 | } 233 | filterList = filterMap.get(measure); 234 | if (filterList == null) { 235 | filterList = new ArrayList(); 236 | filterMap.put(measure, filterList); 237 | } 238 | filterList.add(filter); 239 | return this; 240 | } 241 | 242 | /** 243 | * Returns the list of filters that have been added to the given metric, 244 | * measure pair. 245 | * 246 | * @param metric metric the measure belongs to 247 | * @param measure name of one of the values reported by the metric 248 | * @return list of filters that should be applied when determining the state 249 | * reported with the given measure 250 | */ 251 | public List getFilterList(Metric metric, String measure) { 252 | final Map> filterMap = filterMapMap 253 | .get(metric); 254 | if (filterMap == null) { 255 | return null; 256 | } 257 | return filterMap.get(measure); 258 | } 259 | 260 | /** 261 | * Determines the state that should be reported with the given measure for 262 | * the given timer instance. 263 | * 264 | * @param timer timer instance 265 | * @param measure name of the value reported by the timer whose reported 266 | * state is sought 267 | * @param durationUnit TimeUnit to convert timer's native (nanosecond) 268 | * measurements to 269 | * @return state corresponding to the given measure 270 | */ 271 | public String state(Timer timer, String measure, TimeUnit durationUnit) { 272 | final Snapshot snap = timer.getSnapshot(); 273 | final double value = snapValueMap.get(measure).value(snap) / 274 | durationUnit.toNanos(1); 275 | return state(getFilterList(timer, measure), value); 276 | } 277 | 278 | /** 279 | * Return the state that should be reported for the given measure, histogram 280 | * pair. 281 | * 282 | * @param histogram histogram instance 283 | * @param measure name of a measure included in the histogram report 284 | * @return the state associated to the current value of the measure in the 285 | * histogram, or "ok" if there is no state mapped to this measure, 286 | * histogram pair. 287 | */ 288 | public String state(Histogram histogram, String measure) { 289 | double value; 290 | if (measure.equals(COUNT)) { 291 | value = histogram.getCount(); 292 | } else { 293 | final Snapshot snap = histogram.getSnapshot(); 294 | value = snapValueMap.get(measure).value(snap); 295 | } 296 | return state(getFilterList(histogram, measure), value); 297 | } 298 | 299 | /** 300 | * Return the state that should be reported for the given measure, metered 301 | * instance pair. 302 | * 303 | * @param metered metered instance 304 | * @param measure name of a measure included in the metered instance report 305 | * @return the state associated to the current value of the measure in the 306 | * metered instance, or "ok" if there is no state mapped to this 307 | * measure, metered instance pair. 308 | */ 309 | public String state(Metered metered, String measure) { 310 | final double value = meteredValueMap.get(measure).value(metered); 311 | return state(getFilterList(metered, measure), value); 312 | } 313 | 314 | /** 315 | * Return the state that should be reported for the given counter, based on 316 | * its value. 317 | * 318 | * @param counter Counter instance 319 | * @return the state associated to the current value of the counter in the 320 | * or "ok" if there is no state mapped to this Counter value. 321 | */ 322 | public String state(Counter counter) { 323 | final double value = counter.getCount(); 324 | return state(getFilterList(counter, "count"), value); 325 | } 326 | 327 | /** 328 | * Clear all state mappings associated with the given Metric. 329 | * 330 | * @param metric Metric instance to clear mappings for 331 | */ 332 | public synchronized void clear(Metric metric) { 333 | final Map> filters = filterMapMap.get(metric); 334 | for (Entry> entry : filters.entrySet()) { 335 | entry.getValue().clear(); 336 | } 337 | filters.clear(); 338 | } 339 | 340 | /** 341 | * Clear all mappings for all Metric instances. 342 | */ 343 | public synchronized void clear() { 344 | for (Metric metric : filterMapMap.keySet()) { 345 | clear(metric); 346 | } 347 | } 348 | 349 | /** 350 | * Test all ValueFilters in filters against the given value. If none apply, 351 | * return "ok"; otherwise return the state associated with the last filter 352 | * that applies. 353 | * 354 | * @param filters List of ValueFilters to test 355 | * @param value value to test 356 | * @return "ok" or the state associated with the last filter in the list 357 | * that applies 358 | */ 359 | private String state(List filters, double value) { 360 | String ret = "ok"; 361 | if (filters != null) { 362 | for (ValueFilter filter : filters) { 363 | if (filter.applies(value)) { 364 | ret = filter.getState(); 365 | } 366 | } 367 | } 368 | return ret; 369 | } 370 | } 371 | -------------------------------------------------------------------------------- /metrics3-riemann-reporter/src/test/java/com/codahale/metrics/riemann/RiemannTest.java: -------------------------------------------------------------------------------- 1 | package com.codahale.metrics.riemann; 2 | 3 | import io.riemann.riemann.client.IRiemannClient; 4 | 5 | import org.junit.Test; 6 | 7 | import static org.mockito.Mockito.*; 8 | 9 | public class RiemannTest { 10 | private final IRiemannClient client = mock(IRiemannClient.class); 11 | private final Riemann riemann = new Riemann(client); 12 | 13 | @Test 14 | public void connectsToRiemann() throws Exception { 15 | riemann.connect(); 16 | verify(client).connect(); 17 | } 18 | 19 | @Test 20 | public void disconnectsFromRiemann() throws Exception { 21 | riemann.connect(); 22 | riemann.close(); 23 | verify(client).close(); 24 | } 25 | } 26 | 27 | -------------------------------------------------------------------------------- /metrics3-riemann-reporter/src/test/java/com/codahale/metrics/riemann/ValueFilterTest.java: -------------------------------------------------------------------------------- 1 | package com.codahale.metrics.riemann; 2 | 3 | import static org.junit.Assert.assertFalse; 4 | import static org.junit.Assert.assertTrue; 5 | 6 | import org.junit.Test; 7 | 8 | public class ValueFilterTest { 9 | 10 | @Test 11 | public void testApplies() { 12 | final ValueFilter warnFilter = new ValueFilter.Builder("warn") 13 | .withLower(900).withUpper(1000).build(); 14 | final ValueFilter criticalFilter = new ValueFilter.Builder("critical") 15 | .withLowerExclusive(1000).build(); 16 | final ValueFilter halfOpenFilter = new ValueFilter.Builder("warn") 17 | .withUpperExclusive(300).withLower(100).build(); 18 | assertTrue(warnFilter.applies(920)); 19 | assertFalse(warnFilter.applies(1001)); 20 | assertTrue(criticalFilter.applies(1001)); 21 | assertFalse(criticalFilter.applies(950)); 22 | assertTrue(halfOpenFilter.applies(200)); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /metrics4-riemann-reporter/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 4.0.0 7 | metrics4-riemann-reporter 8 | 9 | riemann-java-client-parent 10 | io.riemann 11 | 0.5.4-SNAPSHOT 12 | 13 | 14 | 15 | 16 | io.dropwizard.metrics 17 | metrics-core 18 | 4.2.0 19 | 20 | 21 | ${project.groupId} 22 | riemann-java-client 23 | ${project.version} 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /metrics4-riemann-reporter/src/main/java/com/codahale/metrics/riemann/Riemann.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Dominic LoBue 3 | * Copyright 2014 Brandon Seibel 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package com.codahale.metrics.riemann; 19 | 20 | import io.riemann.riemann.client.IRiemannClient; 21 | import io.riemann.riemann.client.RiemannClient; 22 | import io.riemann.riemann.client.RiemannBatchClient; 23 | import io.riemann.riemann.client.UnsupportedJVMException; 24 | 25 | 26 | import java.io.Closeable; 27 | import java.io.IOException; 28 | 29 | 30 | public class Riemann implements Closeable { 31 | 32 | IRiemannClient client; 33 | 34 | public Riemann(String host, Integer port) throws IOException { 35 | this(host, port, 10); 36 | } 37 | 38 | public Riemann(String host, Integer port, int batchSize) throws IOException { 39 | this(getClient(host, port, batchSize)); 40 | } 41 | 42 | private static IRiemannClient getClient(String host, Integer port, int batchSize) throws IOException { 43 | IRiemannClient c = RiemannClient.tcp(host, port); 44 | try { 45 | return new RiemannBatchClient(c, batchSize); 46 | } catch (UnsupportedJVMException e) { 47 | return c; 48 | } 49 | } 50 | 51 | public Riemann(IRiemannClient client) { 52 | this.client = client; 53 | } 54 | 55 | public void connect() throws IOException { 56 | if (!client.isConnected()) { 57 | client.connect(); 58 | } 59 | } 60 | 61 | @Override 62 | public void close() throws IOException { 63 | if (client != null) { 64 | client.close(); 65 | } 66 | 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /metrics4-riemann-reporter/src/main/java/com/codahale/metrics/riemann/ValueFilter.java: -------------------------------------------------------------------------------- 1 | package com.codahale.metrics.riemann; 2 | 3 | /** 4 | * Filters values to determine if they fall into a given range via the 5 | * {@link #applies(double)} method. Ranges are intervals, with endpoint 6 | * inclusion configurable. The {@code state} property implicitly binds values in 7 | * the represented range to that state for reporting. 8 | *

9 | * Instances are created using {@link ValueFilter.Builder}. Endpoints not 10 | * specified default to positive or negative infinity, respectively. For 11 | * example, {@code new ValueFilter.Builder("critical").withLower(50).build())} 12 | * creates a ValueFilter instance that binds "critical" to all values greater 13 | * than or equal to 50 and {@code new ValueFilter.Builder("warn") 14 | * .withUpperExclusive(300) 15 | * .withLower(100).build())} creates an instance 16 | * binding "warn" to values greater than or equal to 100 and strictly less than 17 | * 300. 18 | */ 19 | public class ValueFilter { 20 | 21 | /** Lower bound of the interval */ 22 | private final double lower; 23 | 24 | /** Upper bound of the interval */ 25 | private final double upper; 26 | 27 | /** Whether or not the upper bound is included */ 28 | private final boolean upperIncluded; 29 | 30 | /** Whether or not the lower bound is included */ 31 | private final boolean lowerIncluded; 32 | 33 | /** State bound to values in this interval */ 34 | private final String state; 35 | 36 | /** Create a ValueFilter instance using the given builder */ 37 | private ValueFilter(Builder builder) { 38 | this.lower = builder.lower; 39 | this.upper = builder.upper; 40 | this.upperIncluded = builder.upperIncluded; 41 | this.lowerIncluded = builder.lowerIncluded; 42 | this.state = builder.state; 43 | } 44 | 45 | /** 46 | * Determine whether or not the given value falls within the range 47 | * associated with this ValueFilter. 48 | * 49 | * @param value value to test 50 | * @return true iff value is within the target interval 51 | */ 52 | public boolean applies(double value) { 53 | boolean ret = upperIncluded ? value <= upper : value < upper; 54 | return ret && (lowerIncluded ? lower <= value : lower < value); 55 | } 56 | 57 | /** 58 | * @return the state bound to values in this ValueFilter 59 | */ 60 | public String getState() { 61 | return state; 62 | } 63 | 64 | /** 65 | * Builder for ValueFilter instances 66 | */ 67 | public static class Builder { 68 | 69 | /** Lower bound for the interval */ 70 | private double lower = Double.NEGATIVE_INFINITY; 71 | 72 | /** Upper bound for the interval */ 73 | private double upper = Double.POSITIVE_INFINITY; 74 | 75 | /** Whether or not the upper bound is included in the interval */ 76 | private boolean upperIncluded = true; 77 | 78 | /** Whether or not the lower bound is included in the interval */ 79 | private boolean lowerIncluded = true; 80 | 81 | /** State bound to values in the interval */ 82 | private final String state; 83 | 84 | /** 85 | * Create a new builder for ValueFilters bound to the given state 86 | * 87 | * @param state state bound to values in the interval 88 | */ 89 | public Builder(String state) { 90 | this.state = state; 91 | } 92 | 93 | /** 94 | * @param lower inclusive lower bound for the interval 95 | * @return Builder instance with lower set inclusively (i.e. lower bound 96 | * is included in the interval) 97 | */ 98 | public Builder withLower(double lower) { 99 | this.lower = lower; 100 | return this; 101 | } 102 | 103 | /** 104 | * @param upper inclusive upper bound for the interval 105 | * @return Builder instance with upper set inclusively (i.e. upper bound 106 | * is included in the interval) 107 | */ 108 | public Builder withUpper(double upper) { 109 | this.upper = upper; 110 | return this; 111 | } 112 | 113 | /** 114 | * @param lower exclusive lower bound for the interval 115 | * @return Builder instance with lower set exclusively (i.e. lower bound 116 | * is not included in the interval) 117 | */ 118 | public Builder withLowerExclusive(double lower) { 119 | this.lower = lower; 120 | this.lowerIncluded = false; 121 | return this; 122 | }; 123 | 124 | /** 125 | * @param upper exclusive upper bound for the interval 126 | * @return Builder instance with upper set exclusively (i.e. upper bound 127 | * is not included in the interval) 128 | */ 129 | public Builder withUpperExclusive(double upper) { 130 | this.upper = upper; 131 | this.upperIncluded = false; 132 | return this; 133 | } 134 | 135 | /** 136 | * Build a ValueFilter using this Builder instance 137 | * 138 | * @return ValueFilter with properties determined by those of this 139 | * Builder 140 | */ 141 | public ValueFilter build() { 142 | return new ValueFilter(this); 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /metrics4-riemann-reporter/src/main/java/com/codahale/metrics/riemann/ValueFilterMap.java: -------------------------------------------------------------------------------- 1 | package com.codahale.metrics.riemann; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | import java.util.Map.Entry; 8 | import java.util.concurrent.ConcurrentHashMap; 9 | import java.util.concurrent.TimeUnit; 10 | 11 | import com.codahale.metrics.Counter; 12 | import com.codahale.metrics.Histogram; 13 | import com.codahale.metrics.Metered; 14 | import com.codahale.metrics.Metric; 15 | import com.codahale.metrics.Snapshot; 16 | import com.codahale.metrics.Timer; 17 | 18 | /** 19 | * Enables {@link ValueFilter}s to be associated with measures reported by 20 | * Metrics so that state can be computed based on values at report-generation 21 | * time. The {@link #addFilter(Metric, String, ValueFilter)} method associates a 22 | * {@link ValueFilter} with a measure and the {@code state} methods compute what 23 | * state should be reported based on the value of a measure. 24 | *

25 | * For example, {@code 26 | * ValueFilterMap valueFilterMap = new ValueFilterMap(); 27 | * valueFilterMap 28 | .addFilter(timer, ValueFilterMap.MAX, 29 | new ValueFilter.Builder("critical").withLower(50).build()) 30 | .addFilter(timer, ValueFilterMap.MEAN, new ValueFilter.Builder("warn") 31 | .withUpperExclusive(200).withLower(100).build()); 32 | * } Attaches filters to the mean and max values reported by timers so that 33 | * {@code state(timer, "max")} will return "critical" if the max value reported 34 | * by the timer is greater than or equal to 50 and {@code state(timer, "mean")} 35 | * will return "warn" if the mean value is between 100 (inclusive) and 200 36 | * (exclusive). Filters are applied in the order they are added and the last one 37 | * that applies wins. If no filter applies, state methods return "ok". 38 | */ 39 | public class ValueFilterMap { 40 | 41 | // Names for values reported by metrics - call these "measures" 42 | public static final String MAX = "max"; 43 | 44 | public static final String MEAN = "mean"; 45 | 46 | public static final String MIN = "min"; 47 | 48 | public static final String STDDEV = "stddev"; 49 | 50 | public static final String P50 = "p50"; 51 | 52 | public static final String P75 = "p75"; 53 | 54 | public static final String P95 = "p95"; 55 | 56 | public static final String P98 = "p98"; 57 | 58 | public static final String P99 = "p99"; 59 | 60 | public static final String P999 = "p999"; 61 | 62 | public static final String COUNT = "count"; 63 | 64 | public static final String M1_RATE = "m1_rate"; 65 | 66 | public static final String M5_RATE = "m5_rate"; 67 | 68 | public static final String M15_RATE = "m15_rate"; 69 | 70 | public static final String MEAN_RATE = "mean_rate"; 71 | 72 | // TODO: when Java 8 is available, replace this bloated setup with lambdas 73 | // 74 | /** Implementations extract specific values from snapshots */ 75 | interface SnapValue { 76 | 77 | /** @return the snapshot value that this lamba-like thing is keyed on */ 78 | double value(Snapshot snapshot); 79 | } 80 | 81 | /** Implementations extract specific statistics from metered impls */ 82 | interface MeteredValue { 83 | 84 | /** @return the metered value that this lamba-like thing is keyed on */ 85 | double value(Metered metered); 86 | } 87 | 88 | /** 89 | * Map with keys equal to snapshot statistics names and values lambda-like 90 | * objects that extract the named statistic - statically initialized 91 | */ 92 | private static final Map snapValueMap = new HashMap(); 93 | 94 | /** 95 | * Map with keys equal to metered value names and values lambda-like objects 96 | * that extract the named metrics - statically initialized 97 | */ 98 | private static final Map meteredValueMap = new HashMap(); 99 | 100 | /** 101 | * Loads lambda-like functions into maps used to extract values from metrics 102 | */ 103 | static { 104 | snapValueMap.put(MAX, new SnapValue() { 105 | 106 | public double value(Snapshot snapshot) { 107 | return snapshot.getMax(); 108 | } 109 | }); 110 | snapValueMap.put(MEAN, new SnapValue() { 111 | 112 | public double value(Snapshot snapshot) { 113 | return snapshot.getMean(); 114 | } 115 | }); 116 | snapValueMap.put(MIN, new SnapValue() { 117 | 118 | public double value(Snapshot snapshot) { 119 | return snapshot.getMin(); 120 | } 121 | }); 122 | snapValueMap.put(STDDEV, new SnapValue() { 123 | 124 | public double value(Snapshot snapshot) { 125 | return snapshot.getStdDev(); 126 | } 127 | }); 128 | snapValueMap.put(P50, new SnapValue() { 129 | 130 | public double value(Snapshot snapshot) { 131 | return snapshot.getMedian(); 132 | } 133 | }); 134 | snapValueMap.put(P75, new SnapValue() { 135 | 136 | public double value(Snapshot snapshot) { 137 | return snapshot.get75thPercentile(); 138 | } 139 | }); 140 | snapValueMap.put(P95, new SnapValue() { 141 | 142 | public double value(Snapshot snapshot) { 143 | return snapshot.get95thPercentile(); 144 | } 145 | }); 146 | snapValueMap.put(P98, new SnapValue() { 147 | 148 | public double value(Snapshot snapshot) { 149 | return snapshot.get98thPercentile(); 150 | } 151 | }); 152 | snapValueMap.put(P99, new SnapValue() { 153 | 154 | public double value(Snapshot snapshot) { 155 | return snapshot.get99thPercentile(); 156 | } 157 | }); 158 | snapValueMap.put(P999, new SnapValue() { 159 | 160 | public double value(Snapshot snapshot) { 161 | return snapshot.get999thPercentile(); 162 | } 163 | }); 164 | 165 | meteredValueMap.put(COUNT, new MeteredValue() { 166 | 167 | public double value(Metered metered) { 168 | return metered.getCount(); 169 | } 170 | }); 171 | meteredValueMap.put(M1_RATE, new MeteredValue() { 172 | 173 | public double value(Metered metered) { 174 | return metered.getOneMinuteRate(); 175 | } 176 | }); 177 | meteredValueMap.put(M5_RATE, new MeteredValue() { 178 | 179 | public double value(Metered metered) { 180 | return metered.getFiveMinuteRate(); 181 | } 182 | }); 183 | meteredValueMap.put(M15_RATE, new MeteredValue() { 184 | 185 | public double value(Metered metered) { 186 | return metered.getFifteenMinuteRate(); 187 | } 188 | }); 189 | meteredValueMap.put(MEAN_RATE, new MeteredValue() { 190 | 191 | public double value(Metered metered) { 192 | return metered.getMeanRate(); 193 | } 194 | }); 195 | } 196 | 197 | /** 198 | * If m is a Metric, then filterMapMap.get(m) is a map with keys equal to 199 | * measures (constants above) and values equal to ValueFilter instances. 200 | * ValueFilters mapped to measures are applied to determine the state 201 | * associated with the given measure reported to Riemann in m's report. For 202 | * example, if t is a timer, filterMapMap(t) is a map of lists of filters. 203 | * The keys to that map are "max", "mean", "min", etc. - all of the measures 204 | * that t has getters for. The associated value is a list of filters. The 205 | * last filter that applies determines what the reported state is for the 206 | * measure. 207 | */ 208 | private final Map>> filterMapMap = new ConcurrentHashMap>>(); 209 | 210 | public ValueFilterMap() { 211 | super(); 212 | } 213 | 214 | /** 215 | * Add a filter associated with the given measure for the given metric. 216 | * 217 | * @param metric metric instance reporting the measure that the filter 218 | * applies to 219 | * @param measure name of the value reported by the metric that the filter 220 | * applies to 221 | * @param filter ValueFilter instance that, if it applies, may determine the 222 | * state reported for the measure 223 | * @return a reference to *this (for fluent activation) 224 | */ 225 | public ValueFilterMap addFilter(Metric metric, String measure, 226 | ValueFilter filter) { 227 | Map> filterMap = filterMapMap.get(metric); 228 | List filterList; 229 | if (filterMap == null) { 230 | filterMap = new HashMap>(); 231 | filterMapMap.put(metric, filterMap); 232 | } 233 | filterList = filterMap.get(measure); 234 | if (filterList == null) { 235 | filterList = new ArrayList(); 236 | filterMap.put(measure, filterList); 237 | } 238 | filterList.add(filter); 239 | return this; 240 | } 241 | 242 | /** 243 | * Returns the list of filters that have been added to the given metric, 244 | * measure pair. 245 | * 246 | * @param metric metric the measure belongs to 247 | * @param measure name of one of the values reported by the metric 248 | * @return list of filters that should be applied when determining the state 249 | * reported with the given measure 250 | */ 251 | public List getFilterList(Metric metric, String measure) { 252 | final Map> filterMap = filterMapMap 253 | .get(metric); 254 | if (filterMap == null) { 255 | return null; 256 | } 257 | return filterMap.get(measure); 258 | } 259 | 260 | /** 261 | * Determines the state that should be reported with the given measure for 262 | * the given timer instance. 263 | * 264 | * @param timer timer instance 265 | * @param measure name of the value reported by the timer whose reported 266 | * state is sought 267 | * @param durationUnit TimeUnit to convert timer's native (nanosecond) 268 | * measurements to 269 | * @return state corresponding to the given measure 270 | */ 271 | public String state(Timer timer, String measure, TimeUnit durationUnit) { 272 | final Snapshot snap = timer.getSnapshot(); 273 | final double value = snapValueMap.get(measure).value(snap) / 274 | durationUnit.toNanos(1); 275 | return state(getFilterList(timer, measure), value); 276 | } 277 | 278 | /** 279 | * Return the state that should be reported for the given measure, histogram 280 | * pair. 281 | * 282 | * @param histogram histogram instance 283 | * @param measure name of a measure included in the histogram report 284 | * @return the state associated to the current value of the measure in the 285 | * histogram, or "ok" if there is no state mapped to this measure, 286 | * histogram pair. 287 | */ 288 | public String state(Histogram histogram, String measure) { 289 | double value; 290 | if (measure.equals(COUNT)) { 291 | value = histogram.getCount(); 292 | } else { 293 | final Snapshot snap = histogram.getSnapshot(); 294 | value = snapValueMap.get(measure).value(snap); 295 | } 296 | return state(getFilterList(histogram, measure), value); 297 | } 298 | 299 | /** 300 | * Return the state that should be reported for the given measure, metered 301 | * instance pair. 302 | * 303 | * @param metered metered instance 304 | * @param measure name of a measure included in the metered instance report 305 | * @return the state associated to the current value of the measure in the 306 | * metered instance, or "ok" if there is no state mapped to this 307 | * measure, metered instance pair. 308 | */ 309 | public String state(Metered metered, String measure) { 310 | final double value = meteredValueMap.get(measure).value(metered); 311 | return state(getFilterList(metered, measure), value); 312 | } 313 | 314 | /** 315 | * Return the state that should be reported for the given counter, based on 316 | * its value. 317 | * 318 | * @param counter Counter instance 319 | * @return the state associated to the current value of the counter in the 320 | * or "ok" if there is no state mapped to this Counter value. 321 | */ 322 | public String state(Counter counter) { 323 | final double value = counter.getCount(); 324 | return state(getFilterList(counter, "count"), value); 325 | } 326 | 327 | /** 328 | * Clear all state mappings associated with the given Metric. 329 | * 330 | * @param metric Metric instance to clear mappings for 331 | */ 332 | public synchronized void clear(Metric metric) { 333 | final Map> filters = filterMapMap.get(metric); 334 | for (Entry> entry : filters.entrySet()) { 335 | entry.getValue().clear(); 336 | } 337 | filters.clear(); 338 | } 339 | 340 | /** 341 | * Clear all mappings for all Metric instances. 342 | */ 343 | public synchronized void clear() { 344 | for (Metric metric : filterMapMap.keySet()) { 345 | clear(metric); 346 | } 347 | } 348 | 349 | /** 350 | * Test all ValueFilters in filters against the given value. If none apply, 351 | * return "ok"; otherwise return the state associated with the last filter 352 | * that applies. 353 | * 354 | * @param filters List of ValueFilters to test 355 | * @param value value to test 356 | * @return "ok" or the state associated with the last filter in the list 357 | * that applies 358 | */ 359 | private String state(List filters, double value) { 360 | String ret = "ok"; 361 | if (filters != null) { 362 | for (ValueFilter filter : filters) { 363 | if (filter.applies(value)) { 364 | ret = filter.getState(); 365 | } 366 | } 367 | } 368 | return ret; 369 | } 370 | } 371 | -------------------------------------------------------------------------------- /metrics4-riemann-reporter/src/test/java/com/codahale/metrics/riemann/RiemannTest.java: -------------------------------------------------------------------------------- 1 | package com.codahale.metrics.riemann; 2 | 3 | import io.riemann.riemann.client.IRiemannClient; 4 | 5 | import org.junit.Test; 6 | 7 | import static org.mockito.Mockito.*; 8 | 9 | public class RiemannTest { 10 | private final IRiemannClient client = mock(IRiemannClient.class); 11 | private final Riemann riemann = new Riemann(client); 12 | 13 | @Test 14 | public void connectsToRiemann() throws Exception { 15 | riemann.connect(); 16 | verify(client).connect(); 17 | } 18 | 19 | @Test 20 | public void disconnectsFromRiemann() throws Exception { 21 | riemann.connect(); 22 | riemann.close(); 23 | verify(client).close(); 24 | } 25 | } 26 | 27 | -------------------------------------------------------------------------------- /metrics4-riemann-reporter/src/test/java/com/codahale/metrics/riemann/ValueFilterTest.java: -------------------------------------------------------------------------------- 1 | package com.codahale.metrics.riemann; 2 | 3 | import static org.junit.Assert.assertFalse; 4 | import static org.junit.Assert.assertTrue; 5 | 6 | import org.junit.Test; 7 | 8 | public class ValueFilterTest { 9 | 10 | @Test 11 | public void testApplies() { 12 | final ValueFilter warnFilter = new ValueFilter.Builder("warn") 13 | .withLower(900).withUpper(1000).build(); 14 | final ValueFilter criticalFilter = new ValueFilter.Builder("critical") 15 | .withLowerExclusive(1000).build(); 16 | final ValueFilter halfOpenFilter = new ValueFilter.Builder("warn") 17 | .withUpperExclusive(300).withLower(100).build(); 18 | assertTrue(warnFilter.applies(920)); 19 | assertFalse(warnFilter.applies(1001)); 20 | assertTrue(criticalFilter.applies(1001)); 21 | assertFalse(criticalFilter.applies(950)); 22 | assertTrue(halfOpenFilter.applies(200)); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | io.riemann 6 | riemann-java-client-parent 7 | 0.5.4-SNAPSHOT 8 | 9 | riemann-java-client 10 | metrics2-riemann-reporter 11 | metrics3-riemann-reporter 12 | metrics4-riemann-reporter 13 | 14 | pom 15 | Riemann Java Client 16 | Java client for http://riemann.io/ 17 | / 18 | 19 | 20 | UTF-8 21 | UTF-8 22 | 23 | 24 | 25 | 26 | Apache-2.0 27 | https://choosealicense.com/licenses/apache-2.0/ 28 | 29 | 30 | 31 | 32 | 33 | 34 | io.netty 35 | netty-bom 36 | 4.1.79.Final 37 | pom 38 | import 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | junit 47 | junit 48 | 4.13.1 49 | test 50 | 51 | 52 | 53 | org.mockito 54 | mockito-core 55 | 3.12.4 56 | test 57 | 58 | 59 | 60 | 61 | 62 | 63 | org.apache.maven.plugins 64 | maven-compiler-plugin 65 | 3.8.0 66 | 67 | 1.8 68 | 1.8 69 | 70 | 71 | 72 | 73 | org.codehaus.mojo 74 | build-helper-maven-plugin 75 | 3.0.0 76 | 77 | 78 | add-source 79 | generate-sources 80 | 81 | add-source 82 | 83 | 84 | 85 | target/generated-sources 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | org.apache.maven.plugins 94 | maven-source-plugin 95 | 3.0.1 96 | 97 | 98 | attach-sources 99 | 100 | jar-no-fork 101 | 102 | 103 | 104 | 105 | 106 | 107 | org.apache.maven.plugins 108 | maven-javadoc-plugin 109 | 110 | 8 111 | false 112 | 113 | 3.4.0 114 | 115 | 116 | attach-javadocs 117 | 118 | jar 119 | 120 | 121 | 122 | 123 | 124 | 125 | org.apache.felix 126 | maven-bundle-plugin 127 | 3.0.1 128 | true 129 | 130 | 131 | 132 | io.riemann.riemann;-noimport:=true, 133 | io.riemann.riemann.client;-noimport:=true, 134 | com.codahale.metrics.riemann;-noimport:=true, 135 | com.yammer.metrics.reporting;-noimport:=true 136 | 137 | 138 | io.dropwizard.metrics;version="[3.6.1)";resolution:="optional", 139 | com.yammer.metrics;resolution:="optional", 140 | com.yammer.metrics.core;resolution:="optional", 141 | com.yammer.metrics.stats;resolution:="optional", 142 | * 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | clojars 153 | Clojars 154 | https://clojars.org/repo 155 | 156 | 157 | 158 | -------------------------------------------------------------------------------- /riemann-java-client/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | riemann-java-client 6 | 4.0.0 7 | 8 | 9 | riemann-java-client-parent 10 | io.riemann 11 | 0.5.4-SNAPSHOT 12 | 13 | 14 | 15 | 16 | org.slf4j 17 | slf4j-api 18 | 1.7.25 19 | 20 | 21 | org.slf4j 22 | slf4j-simple 23 | 1.7.25 24 | test 25 | 26 | 27 | 28 | org.codehaus.jsr166-mirror 29 | jsr166y 30 | 1.7.0 31 | provided 32 | 33 | 34 | 35 | com.google.protobuf 36 | protobuf-java 37 | 3.16.3 38 | compile 39 | 40 | 41 | 42 | io.netty 43 | netty-codec 44 | 45 | 46 | 47 | io.netty 48 | netty-handler 49 | 50 | 51 | 52 | io.netty 53 | netty-transport 54 | 55 | 56 | 57 | javax.xml.bind 58 | jaxb-api 59 | 2.4.0-b180830.0359 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | maven-antrun-plugin 68 | 69 | 70 | generate-sources 71 | generate-sources 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | run 83 | 84 | 85 | 86 | 87 | 88 | org.apache.maven.plugins 89 | maven-compiler-plugin 90 | 3.8.0 91 | 92 | 1.8 93 | 1.8 94 | 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /riemann-java-client/src/main/java/clojure/lang/IBlockingDeref.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Rich Hickey. All rights reserved. 3 | * The use and distribution terms for this software are covered by the 4 | * Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 5 | * which can be found in the file epl-v10.html at the root of this distribution. 6 | * By using this software in any fashion, you are agreeing to be bound by 7 | * the terms of this license. 8 | * You must not remove this notice, or any other, from this software. 9 | **/ 10 | 11 | /* rich 3/18/11 */ 12 | 13 | package clojure.lang; 14 | 15 | import java.lang.Exception; 16 | 17 | public interface IBlockingDeref { 18 | Object deref(long ms, Object timeoutValue) throws Exception; 19 | } 20 | -------------------------------------------------------------------------------- /riemann-java-client/src/main/java/clojure/lang/IDeref.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Rich Hickey. All rights reserved. 3 | * The use and distribution terms for this software are covered by the 4 | * Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) 5 | * which can be found in the file epl-v10.html at the root of this distribution. 6 | * By using this software in any fashion, you are agreeing to be bound by 7 | * the terms of this license. 8 | * You must not remove this notice, or any other, from this software. 9 | **/ 10 | 11 | /* rich Feb 9, 2009 */ 12 | 13 | package clojure.lang; 14 | 15 | public interface IDeref{ 16 | Object deref() throws Exception; 17 | } 18 | -------------------------------------------------------------------------------- /riemann-java-client/src/main/java/io/riemann/riemann/client/AsynchronizeTransport.java: -------------------------------------------------------------------------------- 1 | package io.riemann.riemann.client; 2 | 3 | // Wraps an asynchronous transport with a concurrent synchronous layer. 4 | 5 | import io.riemann.riemann.Proto.Msg; 6 | import java.io.IOException; 7 | 8 | public class AsynchronizeTransport implements AsynchronousTransport { 9 | public final SynchronousTransport transport; 10 | 11 | public AsynchronizeTransport(final SynchronousTransport transport) { 12 | this.transport = transport; 13 | } 14 | 15 | // Asynchronous path 16 | public Promise sendMessage(final Msg msg) { 17 | final Promise p = new Promise(); 18 | 19 | try { 20 | final Msg response = transport.sendMessage(msg); 21 | if (response == null) { 22 | p.deliver(new UnsupportedOperationException( 23 | transport.toString() + " doesn't support receiving messages.")); 24 | } else { 25 | p.deliver(response); 26 | } 27 | } catch (IOException e) { 28 | p.deliver(e); 29 | } catch (RuntimeException e) { 30 | p.deliver(e); 31 | } 32 | return p; 33 | } 34 | 35 | @Override 36 | public SynchronousTransport transport() { 37 | return transport; 38 | } 39 | 40 | // Lifecycle 41 | @Override 42 | public boolean isConnected() { 43 | return transport.isConnected(); 44 | } 45 | 46 | @Override 47 | public void connect() throws IOException { 48 | transport.connect(); 49 | } 50 | 51 | @Override 52 | public void close() { 53 | transport.close(); 54 | } 55 | 56 | @Override 57 | public void reconnect() throws IOException { 58 | transport.reconnect(); 59 | } 60 | 61 | @Override 62 | public void flush() throws IOException { 63 | transport.flush(); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /riemann-java-client/src/main/java/io/riemann/riemann/client/AsynchronousTransport.java: -------------------------------------------------------------------------------- 1 | package io.riemann.riemann.client; 2 | 3 | import io.riemann.riemann.Proto.Msg; 4 | 5 | public interface AsynchronousTransport extends Transport { 6 | // Schedules a message to be sent, returns a promise which fulfills the 7 | // response. There are *no* guarantees that an asynchronous message will be 8 | // delivered in order, or at all; you *must* dereference the returned promise. 9 | IPromise sendMessage(final Msg msg); 10 | } 11 | -------------------------------------------------------------------------------- /riemann-java-client/src/main/java/io/riemann/riemann/client/ChainPromise.java: -------------------------------------------------------------------------------- 1 | package io.riemann.riemann.client; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | import java.io.IOException; 5 | 6 | // Imagine you're calling a function send() which returns an 7 | // IPromise, and want to return that promise to the caller. 8 | // 9 | // IPromise cleanHouse() { 10 | // return send(); 11 | // } 12 | // 13 | // Problem is, you're not going to call send(); yet. 14 | // 15 | // IPromise cleanHouse() { 16 | // meh(sureDadWhatever); 17 | // 18 | // return // um what goes here??? 19 | // } 20 | // 21 | // You're gonna call it *later*, because you're doing some kind of batching 22 | // async magic. When you *do* call send() you'll be able to hand that result 23 | // off to the client, but not yet. 24 | // 25 | // public void eventually() { 26 | // final IPromise = send(); 27 | // // OK now what? 28 | // } 29 | // 30 | // Solution: construct a ChainPromise *now*, and call .attach(send()) 31 | // on that ChainPromise *later*, which hooks the ChainPromise up to the 32 | // response of send(). Derefs on the ChainPromise wait until .attach() happens, 33 | // then dereference *that*. 34 | // 35 | // JUST ADD MORE LATCHES AND ATOMICREFERENCES, EVERYTHING IS FUCKING FINE 36 | public class ChainPromise implements IPromise { 37 | public final IPromise> inner = new Promise>(); 38 | 39 | public void attach(final IPromise innerValue) { 40 | inner.deliver(innerValue); 41 | } 42 | 43 | @Override 44 | public void deliver(final Object value) { 45 | throw new UnsupportedOperationException("Can't deliver to a chained promise.; deliver to the underlying promise instead?"); 46 | } 47 | 48 | @Override 49 | public T deref() throws IOException { 50 | return inner.deref().deref(); 51 | } 52 | 53 | @Override 54 | public T deref(final long time, final TimeUnit unit) throws IOException { 55 | return deref(time, unit, null); 56 | } 57 | 58 | @Override 59 | public T deref(final long time, final TimeUnit unit, final T timeoutValue) 60 | throws IOException { 61 | return (T) unsafeDeref(time, unit, timeoutValue); 62 | } 63 | 64 | @Override 65 | public Object deref(final long millis, final Object timeoutValue) 66 | throws IOException { 67 | return unsafeDeref(millis, TimeUnit.MILLISECONDS, timeoutValue); 68 | } 69 | 70 | @Override 71 | public Object unsafeDeref(final long time, 72 | final TimeUnit unit, 73 | final Object timeoutValue) 74 | throws IOException { 75 | final long t1 = System.nanoTime(); 76 | 77 | // Extract our attached promise 78 | final Object attached = inner.unsafeDeref(time, unit, timeoutValue); 79 | if (timeoutValue == attached) { 80 | return timeoutValue; 81 | } 82 | 83 | // How much time left? 84 | final long remainingNanos = t1 + unit.toNanos(time) - System.nanoTime(); 85 | 86 | // Since inner.unsafeDeref did *not* return the timeoutValue, it gave us an 87 | // IPromise. 88 | return ((IPromise) attached) 89 | .unsafeDeref(remainingNanos, TimeUnit.NANOSECONDS, timeoutValue); 90 | } 91 | 92 | @Override 93 | public IPromise map(Fn2 f) { 94 | return new MapPromise(this, f); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /riemann-java-client/src/main/java/io/riemann/riemann/client/EventBuilder.java: -------------------------------------------------------------------------------- 1 | package io.riemann.riemann.client; 2 | 3 | import io.riemann.riemann.Proto; 4 | import io.riemann.riemann.Proto.Attribute; 5 | 6 | import java.util.Arrays; 7 | import java.util.HashMap; 8 | import java.util.List; 9 | import java.util.Map; 10 | 11 | public class EventBuilder { 12 | private final Proto.Event.Builder builder; 13 | private final Map attributes = new HashMap(); 14 | 15 | public EventBuilder() { 16 | this.builder = Proto.Event.newBuilder(); 17 | } 18 | 19 | public EventBuilder host(String host) { 20 | if (null == host) { 21 | builder.clearHost(); 22 | } else { 23 | builder.setHost(host); 24 | } 25 | return this; 26 | } 27 | 28 | public EventBuilder service(String service) { 29 | if (null == service) { 30 | builder.clearService(); 31 | } else { 32 | builder.setService(service); 33 | } 34 | return this; 35 | } 36 | 37 | public EventBuilder state(String state) { 38 | if (null == state) { 39 | builder.clearState(); 40 | } else { 41 | builder.setState(state); 42 | } 43 | return this; 44 | } 45 | 46 | public EventBuilder description(String description) { 47 | if (null == description) { 48 | builder.clearDescription(); 49 | } else { 50 | builder.setDescription(description); 51 | } 52 | return this; 53 | } 54 | 55 | public EventBuilder time() { 56 | builder.clearTime(); 57 | builder.clearTimeMicros(); 58 | return this; 59 | } 60 | 61 | public EventBuilder time(float time) { 62 | builder.setTime((long) time); 63 | builder.setTimeMicros((long) (time * 1000000)); 64 | return this; 65 | } 66 | 67 | public EventBuilder time(double time) { 68 | builder.setTime((long) time); 69 | builder.setTimeMicros((long) (time * 1000000)); 70 | return this; 71 | } 72 | 73 | public EventBuilder time(long time) { 74 | builder.setTime(time); 75 | return this; 76 | } 77 | 78 | public EventBuilder metric() { 79 | builder.clearMetricF(); 80 | builder.clearMetricD(); 81 | builder.clearMetricSint64(); 82 | return this; 83 | } 84 | 85 | public EventBuilder metric(byte metric) { 86 | builder.setMetricSint64((long) metric); 87 | builder.setMetricF((float) metric); 88 | return this; 89 | } 90 | 91 | public EventBuilder metric(short metric) { 92 | builder.setMetricSint64((long) metric); 93 | builder.setMetricF((float) metric); 94 | return this; 95 | } 96 | 97 | public EventBuilder metric(int metric) { 98 | builder.setMetricSint64((long) metric); 99 | builder.setMetricF((float) metric); 100 | return this; 101 | } 102 | 103 | public EventBuilder metric(long metric) { 104 | builder.setMetricSint64(metric); 105 | builder.setMetricF((float) metric); 106 | return this; 107 | } 108 | 109 | public EventBuilder metric(float metric) { 110 | builder.setMetricF(metric); 111 | return this; 112 | } 113 | 114 | public EventBuilder metric(double metric) { 115 | builder.setMetricD(metric); 116 | builder.setMetricF((float) metric); 117 | return this; 118 | } 119 | 120 | public EventBuilder tag(String tag) { 121 | builder.addTags(tag); 122 | return this; 123 | } 124 | 125 | public EventBuilder tags(List tags) { 126 | builder.addAllTags(tags); 127 | return this; 128 | } 129 | 130 | public EventBuilder tags(String... tags) { 131 | builder.addAllTags(Arrays.asList(tags)); 132 | return this; 133 | } 134 | 135 | public EventBuilder ttl() { 136 | builder.clearTtl(); 137 | return this; 138 | } 139 | 140 | public EventBuilder ttl(float ttl) { 141 | builder.setTtl(ttl); 142 | return this; 143 | } 144 | 145 | public EventBuilder attribute(String name, String value) { 146 | attributes.put(name, value); 147 | return this; 148 | } 149 | 150 | public EventBuilder attributes(Map attributes) { 151 | this.attributes.putAll(attributes); 152 | return this; 153 | } 154 | 155 | // Returns the compiled Protobuf event for this DSL. Merges in the custom 156 | // attributes map. Can only be called safely once. 157 | public Proto.Event build() { 158 | for (Map.Entry entry : attributes.entrySet()) { 159 | Attribute.Builder attribBuilder = Attribute.newBuilder(); 160 | attribBuilder.setKey(entry.getKey()); 161 | attribBuilder.setValue(entry.getValue()); 162 | builder.addAttributes(attribBuilder); 163 | } 164 | return builder.build(); 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /riemann-java-client/src/main/java/io/riemann/riemann/client/EventDSL.java: -------------------------------------------------------------------------------- 1 | package io.riemann.riemann.client; 2 | 3 | import io.riemann.riemann.Proto.Attribute; 4 | import io.riemann.riemann.Proto.Event; 5 | import io.riemann.riemann.Proto.Msg; 6 | 7 | import java.util.Arrays; 8 | import java.util.List; 9 | import java.util.Map; 10 | import java.util.HashMap; 11 | 12 | public class EventDSL { 13 | public final IRiemannClient client; 14 | public final EventBuilder event; 15 | 16 | public EventDSL(IRiemannClient client) { 17 | this.client = client; 18 | this.event = new EventBuilder(); 19 | try { 20 | this.event.host(java.net.InetAddress.getLocalHost().getHostName()); 21 | } catch (java.net.UnknownHostException e) { 22 | // If we can't get the local host, a null host is perfectly 23 | // acceptable. Caller will know soon enough. :) 24 | } 25 | } 26 | 27 | public EventDSL host(String host) { 28 | this.event.host(host); 29 | return this; 30 | } 31 | 32 | public EventDSL service(String service) { 33 | this.event.service(service); 34 | return this; 35 | } 36 | 37 | public EventDSL state(String state) { 38 | this.event.state(state); 39 | return this; 40 | } 41 | 42 | public EventDSL description(String description) { 43 | this.event.description(description); 44 | return this; 45 | } 46 | 47 | public EventDSL time(Null n) { 48 | this.event.time(); 49 | return this; 50 | } 51 | 52 | public EventDSL time(float time) { 53 | this.event.time(time); 54 | return this; 55 | } 56 | 57 | public EventDSL time(double time) { 58 | this.event.time(time); 59 | return this; 60 | } 61 | public EventDSL time(long time) { 62 | this.event.time(time); 63 | return this; 64 | } 65 | 66 | public EventDSL metric(Null n) { 67 | this.event.metric(); 68 | return this; 69 | } 70 | 71 | public EventDSL metric(byte metric) { 72 | this.event.metric(metric); 73 | return this; 74 | } 75 | 76 | public EventDSL metric(short metric) { 77 | this.event.metric(metric); 78 | return this; 79 | } 80 | 81 | public EventDSL metric(int metric) { 82 | this.event.metric(metric); 83 | return this; 84 | } 85 | 86 | public EventDSL metric(long metric) { 87 | this.event.metric(metric); 88 | return this; 89 | } 90 | 91 | public EventDSL metric(float metric) { 92 | this.event.metric(metric); 93 | return this; 94 | } 95 | 96 | public EventDSL metric(double metric) { 97 | this.event.metric(metric); 98 | return this; 99 | } 100 | 101 | public EventDSL tag(String tag) { 102 | this.event.tag(tag); 103 | return this; 104 | } 105 | 106 | public EventDSL tags(List tags) { 107 | this.event.tags(tags); 108 | return this; 109 | } 110 | 111 | public EventDSL tags(String... tags) { 112 | this.event.tags(tags); 113 | return this; 114 | } 115 | 116 | public EventDSL ttl(Null n) { 117 | this.event.ttl(); 118 | return this; 119 | } 120 | 121 | public EventDSL ttl(float ttl) { 122 | this.event.ttl(ttl); 123 | return this; 124 | } 125 | 126 | public EventDSL attribute(String name, String value) { 127 | this.event.attribute(name, value); 128 | return this; 129 | } 130 | 131 | public EventDSL attributes(Map attributes) { 132 | this.event.attributes(attributes); 133 | return this; 134 | } 135 | 136 | // Returns the compiled Protobuf event for this DSL. Merges in the custom 137 | // attributes map. Can only be called safely once. 138 | public Event build() { 139 | return this.event.build(); 140 | } 141 | 142 | public IPromise send() { 143 | return client.sendEvent(build()); 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /riemann-java-client/src/main/java/io/riemann/riemann/client/ExceptionReporter.java: -------------------------------------------------------------------------------- 1 | package io.riemann.riemann.client; 2 | 3 | /** 4 | * Exception-reporting callback. 5 | * 6 | */ 7 | public interface ExceptionReporter { 8 | public void reportException(final Throwable t); 9 | } 10 | -------------------------------------------------------------------------------- /riemann-java-client/src/main/java/io/riemann/riemann/client/Fn2.java: -------------------------------------------------------------------------------- 1 | package io.riemann.riemann.client; 2 | 3 | // A function from T1 to T2. 4 | public interface Fn2 { 5 | public T2 call(final T1 x); 6 | } 7 | -------------------------------------------------------------------------------- /riemann-java-client/src/main/java/io/riemann/riemann/client/IPromise.java: -------------------------------------------------------------------------------- 1 | package io.riemann.riemann.client; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | import java.io.IOException; 5 | import clojure.lang.IDeref; 6 | import clojure.lang.IBlockingDeref; 7 | 8 | public interface IPromise extends IDeref, IBlockingDeref { 9 | // Fulfill this promise with a single value. Only the first call to 10 | // deliver() will succeed; successive deliveries are noops. 11 | public void deliver(final Object value); 12 | 13 | // Dereference this promise, blocking indefinitely. 14 | public T deref() throws IOException; 15 | 16 | // Dereference this promise, returning null if it times out. 17 | public T deref(final long time, final TimeUnit unit) throws IOException; 18 | 19 | // Dereference this promise, returning a T. 20 | public T deref(final long time, 21 | final TimeUnit unit, 22 | final T timeoutValue) throws IOException; 23 | 24 | // A timed deref that allows any object for the timeout value. You and I 25 | // both know this will return either a T or just timeoutValue, but 26 | // unfortunately, as you probably already know, the Java type system. 27 | public Object unsafeDeref(final long time, 28 | final TimeUnit unit, 29 | final Object timeoutValue) throws IOException; 30 | 31 | // Return a new promise, based on this one, which converts values from T to 32 | // T2 using a function. 33 | public IPromise map(Fn2 f); 34 | } 35 | -------------------------------------------------------------------------------- /riemann-java-client/src/main/java/io/riemann/riemann/client/IRiemannClient.java: -------------------------------------------------------------------------------- 1 | package io.riemann.riemann.client; 2 | 3 | import io.riemann.riemann.Proto.Event; 4 | import io.riemann.riemann.Proto.Query; 5 | import io.riemann.riemann.Proto.Msg; 6 | import java.util.List; 7 | 8 | // The core functionality of any client. 9 | 10 | public interface IRiemannClient extends AsynchronousTransport { 11 | // Send any number of events asynchronously. Returns a promise of a response 12 | // Msg. 13 | IPromise sendEvent(final Event event); 14 | IPromise sendEvents(final Event... events); 15 | IPromise sendEvents(final List events); 16 | 17 | // Send an exception as an event. 18 | IPromise sendException(final String service, final Throwable t); 19 | 20 | // Query the server for all events matching query. Returns a promise of a 21 | // list of events. 22 | IPromise> query(final String q); 23 | 24 | // Create an EventDSL bound to this client 25 | EventDSL event(); 26 | } 27 | -------------------------------------------------------------------------------- /riemann-java-client/src/main/java/io/riemann/riemann/client/MapPromise.java: -------------------------------------------------------------------------------- 1 | package io.riemann.riemann.client; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | import java.io.IOException; 5 | 6 | // Maps a Promise of type T1 to a promise of type T2 by applying a function 7 | // T1->T2 on a call to deref(). Caches the value so f is only invoked once (or 8 | // zero times if a timeout/error occurs). 9 | public class MapPromise implements IPromise { 10 | public static final Object sentinel = new Object(); 11 | 12 | public volatile Object value = sentinel; 13 | public final IPromise p; 14 | public final Fn2 f; 15 | 16 | // Wrap a promise of type T1 with a function from T1->T2. 17 | public MapPromise(final IPromise p, final Fn2 f) { 18 | this.p = p; 19 | this.f = f; 20 | } 21 | 22 | // Call map and trap RuntimeExceptions as values 23 | // Fuck me, I miss macros 24 | public Object mapCapturingExceptions(final T1 x) { 25 | try { 26 | return f.call(x); 27 | } catch (RuntimeException e) { 28 | return e; 29 | } 30 | } 31 | 32 | // Delivering to a map promise delivers to the underlying promise. 33 | public void deliver(final Object value) { 34 | p.deliver(value); 35 | } 36 | 37 | // Deref, applying map to the value returned by the wrapped promise. 38 | public T2 deref() throws IOException { 39 | if (sentinel == value) { 40 | synchronized(this) { 41 | if (sentinel == value) { 42 | value = mapCapturingExceptions(p.deref()); 43 | } 44 | } 45 | } 46 | return (T2) Promise.rehydrate(value); 47 | } 48 | 49 | public T2 deref(final long time, final TimeUnit unit) throws IOException { 50 | return deref(time, unit, null); 51 | } 52 | 53 | public T2 deref(final long time, 54 | final TimeUnit unit, 55 | final T2 timeoutValue) throws IOException { 56 | return (T2) unsafeDeref(time, unit, timeoutValue); 57 | } 58 | 59 | @Override 60 | public Object deref(final long millis, final Object timeoutValue) 61 | throws IOException { 62 | return unsafeDeref(millis, TimeUnit.MILLISECONDS, timeoutValue); 63 | } 64 | 65 | public Object unsafeDeref(final long time, 66 | final TimeUnit unit, 67 | final Object timeoutValue) throws IOException { 68 | if (sentinel == value) { 69 | synchronized(this) { 70 | if (sentinel == value) { 71 | final Object response = p.unsafeDeref(time, unit, sentinel); 72 | if (sentinel == response) { 73 | return timeoutValue; 74 | } else { 75 | // If we *didn't* get our timeout value, unsafeDeref is guaranteed 76 | // to give us a T1. 77 | value = mapCapturingExceptions((T1) response); 78 | } 79 | } 80 | } 81 | } 82 | return (T2) Promise.rehydrate(value); 83 | } 84 | 85 | @Override 86 | public IPromise map(Fn2 f) { 87 | return new MapPromise(this, f); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /riemann-java-client/src/main/java/io/riemann/riemann/client/MsgTooLargeException.java: -------------------------------------------------------------------------------- 1 | package io.riemann.riemann.client; 2 | 3 | // Thrown when a message is too large to send over the specified transport. 4 | public class MsgTooLargeException extends java.io.IOException { 5 | } 6 | -------------------------------------------------------------------------------- /riemann-java-client/src/main/java/io/riemann/riemann/client/MsgValidator.java: -------------------------------------------------------------------------------- 1 | package io.riemann.riemann.client; 2 | 3 | // Validates the integrity of messages, throwing ServerError if one is not OK. 4 | // Returns the message otherwise. 5 | 6 | import io.riemann.riemann.Proto.Msg; 7 | 8 | public class MsgValidator implements Fn2 { 9 | public Msg call(final Msg message) throws ServerError { 10 | if (message.hasOk() && !message.getOk()) { 11 | throw new ServerError(message.getError()); 12 | } 13 | return message; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /riemann-java-client/src/main/java/io/riemann/riemann/client/Null.java: -------------------------------------------------------------------------------- 1 | package io.riemann.riemann.client; 2 | 3 | // null has a type, but you can't use it! Since the JVM doesn't have Maybe[], we'll just make one up instead. 4 | // The *only* acceptable argument for f(Null n) is f(null). 5 | public class Null { 6 | private Null() {} 7 | } -------------------------------------------------------------------------------- /riemann-java-client/src/main/java/io/riemann/riemann/client/OverloadedException.java: -------------------------------------------------------------------------------- 1 | package io.riemann.riemann.client; 2 | 3 | // Thrown when a client is unable to handle additional requests; for example, 4 | // because of high network latencies 5 | public class OverloadedException extends java.io.IOException { 6 | public OverloadedException(final String msg) { 7 | super(msg); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /riemann-java-client/src/main/java/io/riemann/riemann/client/Promise.java: -------------------------------------------------------------------------------- 1 | package io.riemann.riemann.client; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | import java.util.concurrent.CountDownLatch; 5 | import java.util.concurrent.atomic.AtomicReference; 6 | import java.io.IOException; 7 | 8 | public class Promise implements IPromise { 9 | // We store exceptions and actual values in the same reference; on deref(), 10 | // we have to *throw*, not return, the exceptions. This fun takes an Object 11 | // which is either an IOException, a RuntimeException, or a T. Throws the 12 | // exceptions, returns a T. 13 | public static Object rehydrate(final Object value) throws IOException { 14 | if (value instanceof IOException) { 15 | throw (IOException) value; 16 | } else if (value instanceof RuntimeException) { 17 | throw (RuntimeException) value; 18 | } else { 19 | return value; 20 | } 21 | } 22 | 23 | public final CountDownLatch latch = new CountDownLatch(1); 24 | public final AtomicReference ref = new AtomicReference(latch); 25 | 26 | public Promise() { } 27 | 28 | public void deliver(Object value) { 29 | if (0 < latch.getCount() && ref.compareAndSet(latch, value)) { 30 | latch.countDown(); 31 | } 32 | } 33 | 34 | public T deref() throws IOException { 35 | try { 36 | latch.await(); 37 | return (T) rehydrate(ref.get()); 38 | } catch (InterruptedException e) { 39 | return null; 40 | } 41 | } 42 | 43 | public T deref(final long time, final TimeUnit unit) throws IOException { 44 | return deref(time, unit, null); 45 | } 46 | 47 | // Type-safe deref 48 | public T deref(final long time, 49 | final TimeUnit unit, 50 | final T timeoutValue) throws IOException { 51 | // My kingdom for a union type 52 | return (T) unsafeDeref(time, unit, timeoutValue); 53 | } 54 | 55 | @Override 56 | public Object deref(final long millis, final Object timeoutValue) 57 | throws IOException { 58 | return unsafeDeref(millis, TimeUnit.MILLISECONDS, timeoutValue); 59 | } 60 | 61 | // A timed deref that allows any object for the timeout value. You and I 62 | // both know this will return either a T1 or just timeoutValue, but 63 | // unfortunately, as you probably already know, the Java type system. 64 | public Object unsafeDeref(final long time, 65 | final TimeUnit unit, 66 | final Object timeoutValue) throws IOException { 67 | try { 68 | if (latch.await(time, unit)) { 69 | return (T) rehydrate(ref.get()); 70 | } else { 71 | return timeoutValue; 72 | } 73 | } catch (InterruptedException e) { 74 | return timeoutValue; 75 | } 76 | } 77 | 78 | // Return a new promise, based on this one, which converts values from T to 79 | // T2 using a function. 80 | @Override 81 | public IPromise map(Fn2 f) { 82 | return new MapPromise(this, f); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /riemann-java-client/src/main/java/io/riemann/riemann/client/ReconnectHandler.java: -------------------------------------------------------------------------------- 1 | package io.riemann.riemann.client; 2 | 3 | import io.netty.bootstrap.Bootstrap; 4 | import io.netty.channel.ChannelFuture; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.channel.ChannelInboundHandlerAdapter; 7 | import io.netty.channel.group.ChannelGroup; 8 | import io.netty.handler.timeout.ReadTimeoutException; 9 | import java.net.ConnectException; 10 | import java.util.concurrent.TimeUnit; 11 | import java.util.concurrent.atomic.AtomicLong; 12 | 13 | public class ReconnectHandler extends ChannelInboundHandlerAdapter { 14 | private final Bootstrap bootstrap; 15 | private final ChannelGroup channels; 16 | public long startTime = -1; 17 | public final AtomicLong delay; 18 | public final TimeUnit unit; 19 | 20 | public ReconnectHandler(Bootstrap bootstrap, ChannelGroup channels, AtomicLong delay, TimeUnit unit) { 21 | this.bootstrap = bootstrap; 22 | this.channels = channels; 23 | this.delay = delay; 24 | this.unit = unit; 25 | } 26 | 27 | @Override 28 | public void channelUnregistered(ChannelHandlerContext ctx) throws Exception { 29 | try { 30 | ctx.executor().schedule(new Runnable() { 31 | @Override 32 | public void run() { 33 | ChannelFuture channelFuture = bootstrap.connect(); 34 | channels.add(channelFuture.channel()); 35 | } 36 | }, delay.get(), unit); 37 | } catch (java.lang.IllegalStateException ex) { 38 | // The executor must have been stopped. 39 | } 40 | super.channelInactive(ctx); 41 | } 42 | 43 | @Override 44 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 45 | if (startTime < 0) { 46 | startTime = System.currentTimeMillis(); 47 | } 48 | super.channelActive(ctx); 49 | } 50 | 51 | @Override 52 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable e) { 53 | final Throwable cause = e.getCause(); 54 | 55 | if (cause instanceof ConnectException) { 56 | startTime = -1; 57 | } else if (cause instanceof ReadTimeoutException) { 58 | // The connection was OK but there was no traffic for the last period. 59 | } else { 60 | ctx.write(e); 61 | } 62 | ctx.channel().close(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /riemann-java-client/src/main/java/io/riemann/riemann/client/RiemannBatchClient.java: -------------------------------------------------------------------------------- 1 | package io.riemann.riemann.client; 2 | 3 | // Wraps any Riemann client for use in high-throughput loads. Behaves 4 | // exactly like the underlying client, except that calls to sendEvents and 5 | // sendEventsWithAck (including client.event()...send()), are 6 | // batched together into single messages for more efficient transfer. 7 | // 8 | // Can improve bulk throughput by at least an order of magnitude, without 9 | // requiring coordination from writer threads--at the cost of significantly 10 | // higher latencies. 11 | // 12 | // Note that Messages do not explain *which* events fail. Every event in a 13 | // message may raise an exception because of a problem with a single event. 14 | // There is no guarantee as to *which* events were successfully received. Since 15 | // Riemann considers almost every event valid, and will only return failures in 16 | // the event of resource problems, this should be acceptable for most 17 | // scenarios. 18 | // 19 | // To maximize throughput, BatchingRiemannClient is purely reactive, lockfree, 20 | // and makes no guarantees about event latency. Latency of sendEvents() may vary 21 | // widely, depending on whether or not that call flushed the buffer. You may 22 | // call flush() to force buffered events to be written. Flush is automatically 23 | // called at disconnect(). Calls to flush() may not clear the full buffer if 24 | // other threads are actively writing events. 25 | // 26 | // If you need finer-grained control over events, access the underlying client 27 | // directly. Arbitrarily many BatchingRiemannClients may operate over a single 28 | // underlying client. 29 | 30 | import java.util.concurrent.atomic.AtomicInteger; 31 | import java.util.concurrent.atomic.AtomicLong; 32 | import java.util.concurrent.LinkedTransferQueue; 33 | import java.util.ArrayList; 34 | import java.util.Arrays; 35 | import java.util.List; 36 | import io.riemann.riemann.Proto.Msg; 37 | import io.riemann.riemann.Proto.Event; 38 | import java.io.IOException; 39 | 40 | public class RiemannBatchClient implements IRiemannClient { 41 | public final int batchSize; 42 | public final AtomicInteger bufferSize = new AtomicInteger(); 43 | public final LinkedTransferQueue buffer; 44 | public final IRiemannClient client; 45 | 46 | // Maximum time, in ms, we can wait for an event to be processed. 47 | public final AtomicLong readPromiseTimeout = new AtomicLong(5000); 48 | 49 | public RiemannBatchClient(final IRiemannClient client) 50 | throws UnsupportedJVMException { 51 | this(client, 10); 52 | } 53 | 54 | public RiemannBatchClient(final IRiemannClient client, 55 | final int batchSize) 56 | throws UnsupportedJVMException { 57 | this.client = client; 58 | this.batchSize = batchSize; 59 | this.buffer = new java.util.concurrent.LinkedTransferQueue(); 60 | } 61 | 62 | @Override 63 | public IPromise sendMessage(final Msg message) { 64 | return client.sendMessage(message); 65 | } 66 | 67 | @Override 68 | public IPromise sendEvents(final List events) { 69 | final ChainPromise p = new ChainPromise(); 70 | 71 | // Queue up all events with this IPromise. 72 | for (Event event : events) { 73 | queue(new Write(event, p)); 74 | } 75 | 76 | return p; 77 | } 78 | 79 | @Override 80 | public IPromise sendEvents(final Event... events) { 81 | return sendEvents(Arrays.asList(events)); 82 | } 83 | 84 | @Override 85 | public IPromise sendEvent(final Event event) { 86 | final ChainPromise p = new ChainPromise(); 87 | queue(new Write(event, p)); 88 | return p; 89 | } 90 | 91 | @Override 92 | public IPromise sendException(final String service, final Throwable t) { 93 | return RiemannClient.sendException(this, service, t); 94 | } 95 | 96 | @Override 97 | public IPromise> query(final String q) { 98 | return client.query(q); 99 | } 100 | 101 | @Override 102 | public EventDSL event() { 103 | return new EventDSL(this); 104 | } 105 | 106 | // Hey, I just called you 107 | // And this is crazy 108 | // But take this event 109 | // And flush queues maybe 110 | public void queue(final Write write) { 111 | buffer.put(write); 112 | if (batchSize <= bufferSize.addAndGet(1)) { 113 | flush(); 114 | } 115 | } 116 | 117 | // Flushes up to batchSize writes from the queue, and fulfills their 118 | // promises. Should never throw; any exceptions go to the corresponding 119 | // promises. 120 | public int flush2() { 121 | final int maxWrites = Math.min(batchSize, bufferSize.get()); 122 | 123 | // Allocate space for writes 124 | final ArrayList writes = new ArrayList(maxWrites); 125 | 126 | // Suck down elements from queue 127 | buffer.drainTo(writes, maxWrites); 128 | 129 | // Update count 130 | bufferSize.addAndGet(-1 * writes.size()); 131 | 132 | // Build message 133 | final Msg.Builder message = Msg.newBuilder(); 134 | for (Write write : writes) { 135 | message.addEvents(write.event); 136 | } 137 | 138 | // Send message 139 | final IPromise clientPromise = client.sendMessage(message.build()); 140 | 141 | // And hook up all the response promises 142 | for (Write write : writes) { 143 | write.promise.attach(clientPromise); 144 | } 145 | 146 | try { 147 | client.flush(); 148 | } catch (IOException e) { 149 | // not actually thrown by any implementation 150 | } 151 | return writes.size(); 152 | } 153 | 154 | @Override 155 | public void flush() { 156 | flush2(); 157 | } 158 | 159 | @Override 160 | public boolean isConnected() { 161 | return client.isConnected(); 162 | } 163 | 164 | @Override 165 | public void connect() throws IOException { 166 | client.connect(); 167 | } 168 | 169 | @Override 170 | public void close() { 171 | try { 172 | flush(); 173 | } finally { 174 | client.close(); 175 | } 176 | } 177 | 178 | @Override 179 | public void reconnect() throws IOException { 180 | client.reconnect(); 181 | } 182 | 183 | // Returns the underlying client 184 | @Override 185 | public Transport transport() { 186 | return client; 187 | } 188 | 189 | // Combines an Event with a promise to fulfill when received. 190 | public class Write { 191 | public final Event event; 192 | public final ChainPromise promise; 193 | 194 | public Write(final Event event, final ChainPromise promise) { 195 | this.event = event; 196 | this.promise = promise; 197 | } 198 | } 199 | } 200 | -------------------------------------------------------------------------------- /riemann-java-client/src/main/java/io/riemann/riemann/client/RiemannClient.java: -------------------------------------------------------------------------------- 1 | package io.riemann.riemann.client; 2 | 3 | import java.io.IOException; 4 | import java.net.InetSocketAddress; 5 | import java.util.Arrays; 6 | import java.util.Collections; 7 | import java.util.List; 8 | 9 | import io.riemann.riemann.Proto.Event; 10 | import io.riemann.riemann.Proto.Query; 11 | import io.riemann.riemann.Proto.Msg; 12 | 13 | // The standard client. 14 | public class RiemannClient implements IRiemannClient { 15 | // Vars 16 | public static final MsgValidator validate = new MsgValidator(); 17 | 18 | // Send an exception over a client. 19 | public static IPromise sendException(final IRiemannClient client, 20 | final String service, 21 | final Throwable t) { 22 | // Format message and stacktrace 23 | final StringBuilder desc = new StringBuilder(); 24 | desc.append(t.toString()); 25 | desc.append("\n\n"); 26 | for (StackTraceElement e : t.getStackTrace()) { 27 | desc.append(e); 28 | desc.append("\n"); 29 | } 30 | 31 | // Build event and send 32 | return client.event() 33 | .service(service) 34 | .state("error") 35 | .tag("exception") 36 | .tag(t.getClass().getSimpleName()) 37 | .description(desc.toString()) 38 | .send(); 39 | } 40 | 41 | // Wrap any transport 42 | public static RiemannClient wrap(final SynchronousTransport t) { 43 | return new RiemannClient(t); 44 | } 45 | 46 | public static RiemannClient wrap(final AsynchronousTransport t) { 47 | return new RiemannClient(t); 48 | } 49 | 50 | // TCP constructors 51 | public static RiemannClient tcp(final InetSocketAddress remoteAddress) throws IOException { 52 | return wrap(new TcpTransport(remoteAddress)); 53 | } 54 | 55 | public static RiemannClient tcp(final InetSocketAddress remoteAddress, final InetSocketAddress localAddress) throws IOException { 56 | return wrap(new TcpTransport(remoteAddress,localAddress)); 57 | } 58 | 59 | public static RiemannClient tcp(final String remoteHost, final int remotePort) throws IOException{ 60 | return wrap(new TcpTransport(remoteHost, remotePort)); 61 | } 62 | 63 | public static RiemannClient tcp(final String remoteHost, final int remotePort, final String localHost, final int localPort) throws IOException{ 64 | return wrap(new TcpTransport(remoteHost, remotePort, localHost, localPort)); 65 | } 66 | 67 | public static RiemannClient tcp(final String remoteHost) throws IOException { 68 | return wrap(new TcpTransport(remoteHost)); 69 | } 70 | 71 | public static RiemannClient tcp(final String remoteHost, final String localHost) throws IOException { 72 | return wrap(new TcpTransport(remoteHost, localHost)); 73 | } 74 | 75 | public static RiemannClient tcp(final int remotePort) throws IOException { 76 | return wrap(new TcpTransport(remotePort)); 77 | } 78 | 79 | // UDP constructors 80 | // STOP REPEATING YOURSELF KYLE! STOP REPEATING YOURSELF KYLE! 81 | public static RiemannClient udp(final InetSocketAddress remoteAddress) throws IOException { 82 | return wrap(new UdpTransport(remoteAddress)); 83 | } 84 | 85 | public static RiemannClient udp(final InetSocketAddress remoteAddress, final InetSocketAddress localAddress) throws IOException { 86 | return wrap(new UdpTransport(remoteAddress,localAddress)); 87 | } 88 | 89 | public static RiemannClient udp(final String remoteHost, final int remotePort) throws IOException { 90 | return wrap(new UdpTransport(remoteHost, remotePort)); 91 | } 92 | 93 | public static RiemannClient udp(final String remoteHost, final int remotePort, final String localHost, final int localPort) throws IOException{ 94 | return wrap(new UdpTransport(remoteHost, remotePort, localHost, localPort)); 95 | } 96 | 97 | public static RiemannClient udp(final String remoteHost) throws IOException { 98 | return wrap(new UdpTransport(remoteHost)); 99 | } 100 | 101 | public static RiemannClient udp(final String remoteHost, final String localHost) throws IOException { 102 | return wrap(new UdpTransport(remoteHost, localHost)); 103 | } 104 | 105 | public static RiemannClient udp(final int remotePort) throws IOException { 106 | return wrap(new UdpTransport(remotePort)); 107 | } 108 | 109 | 110 | // Vars 111 | public volatile RiemannScheduler scheduler = null; 112 | public final AsynchronousTransport transport; 113 | 114 | 115 | // Transport constructors 116 | public RiemannClient(final SynchronousTransport t) { 117 | this(new AsynchronizeTransport(t)); 118 | } 119 | 120 | public RiemannClient(final AsynchronousTransport t) { 121 | this.transport = t; 122 | } 123 | 124 | 125 | // Create a new event to send over this client 126 | @Override 127 | public EventDSL event() { 128 | return new EventDSL(this); 129 | } 130 | 131 | // Send and receive messages 132 | @Override 133 | public IPromise sendMessage(final Msg m) { 134 | return transport.sendMessage(m).map(validate); 135 | } 136 | 137 | @Override 138 | public IPromise sendEvent(final Event event) { 139 | return sendMessage(Msg.newBuilder().addEvents(event).build()); 140 | } 141 | 142 | @Override 143 | public IPromise sendEvents(final Event... events) { 144 | return sendEvents(Arrays.asList(events)); 145 | } 146 | 147 | @Override 148 | public IPromise sendEvents(final List events) { 149 | return sendMessage(Msg.newBuilder().addAllEvents(events).build()); 150 | } 151 | 152 | @Override 153 | public IPromise sendException(final String service, final Throwable t) { 154 | return RiemannClient.sendException(this, service, t); 155 | } 156 | 157 | @Override 158 | public IPromise> query(final String q) { 159 | return sendMessage( 160 | Msg.newBuilder() 161 | .setQuery(Query.newBuilder().setString(q).build()) 162 | .build()) 163 | .map(new Fn2>() { 164 | public List call(final Msg m) { 165 | return Collections.unmodifiableList(m.getEventsList()); 166 | } 167 | }); 168 | } 169 | 170 | 171 | // Transport lifecycle 172 | @Override 173 | public Transport transport() { 174 | return transport; 175 | } 176 | 177 | @Override 178 | public void connect() throws IOException { 179 | transport.connect(); 180 | } 181 | 182 | @Override 183 | public boolean isConnected() { 184 | return transport.isConnected(); 185 | } 186 | 187 | @Override 188 | public void close() { 189 | transport.close(); 190 | } 191 | 192 | @Override 193 | public void reconnect() throws IOException { 194 | transport.reconnect(); 195 | } 196 | 197 | @Override 198 | public void flush() throws IOException { 199 | transport.flush(); 200 | } 201 | 202 | 203 | // Returns the scheduler for this client. Creates the scheduler on first use. 204 | public synchronized RiemannScheduler scheduler() { 205 | if (scheduler == null) { 206 | scheduler = new RiemannScheduler(this); 207 | } 208 | return scheduler; 209 | } 210 | } 211 | -------------------------------------------------------------------------------- /riemann-java-client/src/main/java/io/riemann/riemann/client/RiemannScheduler.java: -------------------------------------------------------------------------------- 1 | package io.riemann.riemann.client; 2 | 3 | import java.util.concurrent.ScheduledFuture; 4 | import java.util.concurrent.ScheduledThreadPoolExecutor; 5 | import java.util.concurrent.TimeUnit; 6 | 7 | // Supports periodic reporting of events. 8 | public class RiemannScheduler { 9 | public static abstract class Task { 10 | public abstract void run(IRiemannClient r); 11 | } 12 | 13 | public final ScheduledThreadPoolExecutor pool; 14 | public final IRiemannClient client; 15 | 16 | public RiemannScheduler(final IRiemannClient client) { 17 | this(client, 1); 18 | } 19 | 20 | // Finer control over threadpool 21 | public RiemannScheduler(final IRiemannClient client, int poolSize) { 22 | this.client = client; 23 | pool = new ScheduledThreadPoolExecutor(poolSize); 24 | pool.setContinueExistingPeriodicTasksAfterShutdownPolicy(false); 25 | pool.setExecuteExistingDelayedTasksAfterShutdownPolicy(false); 26 | } 27 | 28 | public void shutdown() { 29 | pool.shutdown(); 30 | } 31 | 32 | // Converts a callback to a runnable by closing over this pool's client 33 | protected Runnable runnableCallback(final Task c) { 34 | return new Runnable() { 35 | @Override 36 | public void run() { 37 | c.run(client); 38 | } 39 | }; 40 | } 41 | 42 | // Schedule an arbitrary runnable to be run periodically; useful when 43 | // you already have a client handy. Interval is in ms. 44 | public ScheduledFuture every(long interval, Runnable f) { 45 | return every(interval, 0, f); 46 | } 47 | 48 | public ScheduledFuture every(long interval, Task c) { 49 | return every(interval, runnableCallback(c)); 50 | } 51 | 52 | // Schedule an arbitrary runnable to be run periodically. Adjustable 53 | // units. 54 | public ScheduledFuture every(long interval, TimeUnit unit, Runnable f) { 55 | return every(interval, 0, unit, f); 56 | } 57 | 58 | public ScheduledFuture every(long interval, TimeUnit unit, Task c) { 59 | return every(interval, unit, runnableCallback(c)); 60 | } 61 | 62 | // Schedule an arbitrary runnable to be run periodically, with initial 63 | // delay. Times in ms. 64 | public ScheduledFuture every(long interval, long delay, Runnable f) { 65 | return every(interval, delay, TimeUnit.MILLISECONDS, f); 66 | } 67 | 68 | public ScheduledFuture every(long interval, long delay, Task c) { 69 | return every(interval, delay, runnableCallback(c)); 70 | } 71 | 72 | // Schedule an arbitrary runnable to be run periodically, with initial 73 | // delay and adjustable units. 74 | public ScheduledFuture every(long interval, long delay, TimeUnit unit, Runnable f) { 75 | return pool.scheduleAtFixedRate(f, delay, interval, unit); 76 | } 77 | 78 | public ScheduledFuture every(long interval, long delay, TimeUnit unit, Task c) { 79 | return every(interval, delay, unit, runnableCallback(c)); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /riemann-java-client/src/main/java/io/riemann/riemann/client/SSL.java: -------------------------------------------------------------------------------- 1 | package io.riemann.riemann.client; 2 | 3 | import java.io.*; 4 | import java.net.InetSocketAddress; 5 | import java.security.cert.*; 6 | import java.security.Key; 7 | import java.security.KeyFactory; 8 | import java.security.KeyManagementException; 9 | import java.security.KeyPair; 10 | import java.security.KeyStore; 11 | import java.security.KeyStoreException; 12 | import java.security.NoSuchAlgorithmException; 13 | import java.security.NoSuchProviderException; 14 | import java.security.PrivateKey; 15 | import java.security.PublicKey; 16 | import java.security.spec.*; 17 | import java.security.UnrecoverableKeyException; 18 | import java.util.regex.Matcher; 19 | import java.util.regex.Pattern; 20 | import java.util.Scanner; 21 | import javax.net.ssl.*; 22 | import javax.xml.bind.DatatypeConverter; 23 | 24 | public class SSL { 25 | // You know, a mandatory password stored in memory so we can... encrypt... 26 | // data stored in memory. 27 | public static char[] keyStorePassword = 28 | "GheesBetDyPhuvwotNolofamLydMues9".toCharArray(); 29 | 30 | // The X.509 certificate factory 31 | public static CertificateFactory X509CertFactory() throws 32 | CertificateException { 33 | return CertificateFactory.getInstance("X.509"); 34 | } 35 | 36 | // An RSA key factory 37 | public static KeyFactory RSAKeyFactory() throws NoSuchAlgorithmException { 38 | return KeyFactory.getInstance("RSA"); 39 | } 40 | 41 | // Parses a base64-encoded string to a byte array 42 | public static byte[] base64toBinary(final String string) { 43 | return DatatypeConverter.parseBase64Binary(string); 44 | } 45 | 46 | // Turns a filename into an inputstream 47 | public static FileInputStream inputStream(final String fileName) throws 48 | FileNotFoundException { 49 | return new FileInputStream(new File(fileName)); 50 | } 51 | 52 | // Reads a filename into a string 53 | public static String slurp(final String file) throws FileNotFoundException { 54 | return new Scanner(new File(file)).useDelimiter("\\Z").next(); 55 | } 56 | 57 | // Loads an X.509 certificate from a file. 58 | public static X509Certificate loadCertificate(final String file) throws 59 | IOException, CertificateException { 60 | FileInputStream stream = null; 61 | try { 62 | stream = inputStream(file); 63 | return (X509Certificate) X509CertFactory().generateCertificate(stream); 64 | } finally { 65 | if (stream != null) { 66 | stream.close(); 67 | } 68 | } 69 | } 70 | 71 | // Loads a public key from a .crt file. 72 | public static PublicKey publicKey(final String file) throws IOException, 73 | CertificateException { 74 | return loadCertificate(file).getPublicKey(); 75 | } 76 | 77 | // Loads a private key from a PKCS8 file. 78 | public static PrivateKey privateKey(final String file) throws 79 | FileNotFoundException, NoSuchAlgorithmException, InvalidKeySpecException { 80 | final Pattern p = Pattern.compile( 81 | "^-----BEGIN ?.*? PRIVATE KEY-----$(.+)^-----END ?.*? PRIVATE KEY-----$", 82 | Pattern.MULTILINE | Pattern.DOTALL); 83 | 84 | final Matcher matcher = p.matcher(slurp(file)); 85 | matcher.find(); 86 | return RSAKeyFactory().generatePrivate( 87 | new PKCS8EncodedKeySpec( 88 | base64toBinary( 89 | matcher.group(1)))); 90 | } 91 | 92 | // Makes a KeyStore from a PKCS8 private key file, a public cert file, and the 93 | // signing CA certificate. 94 | public static KeyStore keyStore(final String keyFile, 95 | final String certFile, 96 | final String caCertFile) throws 97 | FileNotFoundException, IOException, KeyStoreException, 98 | NoSuchAlgorithmException, InvalidKeySpecException, CertificateException { 99 | final Key key = privateKey(keyFile); 100 | final Certificate cert = loadCertificate(certFile); 101 | final Certificate caCert = loadCertificate(caCertFile); 102 | final KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType()); 103 | keyStore.load(null, null); 104 | // alias, private key, password, certificate chain 105 | keyStore.setKeyEntry("key", key, keyStorePassword, 106 | new Certificate[] { cert }); 107 | return keyStore; 108 | } 109 | 110 | // Makes a trust store, suitable for backing a TrustManager, out of a CA cert 111 | // file. 112 | public static KeyStore trustStore(final String caCertFile) throws 113 | KeyStoreException, IOException, NoSuchAlgorithmException, 114 | CertificateException { 115 | final KeyStore keyStore = KeyStore.getInstance("JKS"); 116 | keyStore.load(null, null); 117 | keyStore.setCertificateEntry("cacert", loadCertificate(caCertFile)); 118 | return keyStore; 119 | } 120 | 121 | // An X.509 trust manager for a KeyStore. 122 | public static TrustManager trustManager(final KeyStore keyStore) throws 123 | NoSuchAlgorithmException, KeyStoreException, NoSuchProviderException { 124 | final TrustManagerFactory factory = 125 | TrustManagerFactory.getInstance("SunX509", "SunJSSE"); 126 | synchronized(factory) { 127 | factory.init(keyStore); 128 | for (TrustManager tm : factory.getTrustManagers()) { 129 | if (tm instanceof X509TrustManager) { 130 | return tm; 131 | } 132 | } 133 | return null; 134 | } 135 | } 136 | 137 | // An X.509 key manager for a KeyStore 138 | public static KeyManager keyManager(final KeyStore keyStore) throws 139 | NoSuchAlgorithmException, KeyStoreException, NoSuchProviderException, 140 | UnrecoverableKeyException { 141 | final KeyManagerFactory factory = 142 | KeyManagerFactory.getInstance("SunX509", "SunJSSE"); 143 | synchronized(factory) { 144 | factory.init(keyStore, keyStorePassword); 145 | for (KeyManager km : factory.getKeyManagers()) { 146 | if (km instanceof X509KeyManager) { 147 | return km; 148 | } 149 | } 150 | return null; 151 | } 152 | } 153 | 154 | // Builds an SSL Context from a PKCS8 key file, a certificate file, and a 155 | // trusted CA certificate used to verify peers. 156 | public static SSLContext sslContext(final String keyFile, 157 | final String certFile, 158 | final String caCertFile) throws 159 | KeyManagementException, NoSuchAlgorithmException, FileNotFoundException, 160 | KeyStoreException, IOException, InvalidKeySpecException, 161 | CertificateException, NoSuchProviderException, UnrecoverableKeyException { 162 | 163 | final KeyManager keyManager = keyManager( 164 | keyStore(keyFile, certFile, caCertFile)); 165 | final TrustManager trustManager = trustManager(trustStore(caCertFile)); 166 | final SSLContext context = SSLContext.getInstance("TLS"); 167 | context.init(new KeyManager[] { keyManager }, 168 | new TrustManager[] { trustManager }, 169 | null); 170 | return context; 171 | } 172 | 173 | public static SSLContext uncheckedSSLContext(final String keyFile, final String certFile, final String caCertFile) { 174 | // hack hack hack 175 | try { 176 | return sslContext(keyFile, certFile, caCertFile); 177 | } catch (KeyManagementException e) { 178 | throw new RuntimeException(e); 179 | } catch (NoSuchAlgorithmException e) { 180 | throw new RuntimeException(e); 181 | } catch (FileNotFoundException e) { 182 | throw new RuntimeException(e); 183 | } catch (KeyStoreException e) { 184 | throw new RuntimeException(e); 185 | } catch (IOException e) { 186 | throw new RuntimeException(e); 187 | } catch (InvalidKeySpecException e) { 188 | throw new RuntimeException(e); 189 | } catch (CertificateException e) { 190 | throw new RuntimeException(e); 191 | } catch (NoSuchProviderException e) { 192 | throw new RuntimeException(e); 193 | } catch (UnrecoverableKeyException e) { 194 | throw new RuntimeException(e); 195 | } 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /riemann-java-client/src/main/java/io/riemann/riemann/client/ServerError.java: -------------------------------------------------------------------------------- 1 | package io.riemann.riemann.client; 2 | 3 | public class ServerError extends RuntimeException { 4 | public final String message; 5 | 6 | public ServerError(String message) { 7 | this.message = message; 8 | } 9 | 10 | public String getMessage() { 11 | return message; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /riemann-java-client/src/main/java/io/riemann/riemann/client/SimpleUdpTransport.java: -------------------------------------------------------------------------------- 1 | package io.riemann.riemann.client; 2 | 3 | import java.io.IOException; 4 | import java.net.DatagramPacket; 5 | import java.net.DatagramSocket; 6 | import java.net.InetAddress; 7 | import java.net.InetSocketAddress; 8 | import java.net.UnknownHostException; 9 | 10 | import io.riemann.riemann.Proto.Msg; 11 | 12 | public class SimpleUdpTransport implements SynchronousTransport { 13 | 14 | public static final int DEFAULT_PORT = 5555; 15 | 16 | private volatile DatagramSocket socket; 17 | private volatile boolean connected = false; 18 | 19 | private final InetSocketAddress remoteAddress; 20 | private final InetSocketAddress localAddress; 21 | 22 | public SimpleUdpTransport(final InetSocketAddress remoteAddress) { 23 | this.remoteAddress = remoteAddress; 24 | this.localAddress = null; 25 | } 26 | 27 | public SimpleUdpTransport(final InetSocketAddress remoteAddress, final InetSocketAddress localAddress) { 28 | this.remoteAddress = remoteAddress; 29 | this.localAddress = localAddress; 30 | } 31 | 32 | public SimpleUdpTransport(final String host, final int port) throws IOException { 33 | this(InetSocketAddress.createUnresolved(host, port)); 34 | } 35 | 36 | public SimpleUdpTransport(final String remoteHost, final int remotePort, final String localHost, final int localPort) throws IOException { 37 | this(InetSocketAddress.createUnresolved(remoteHost, remotePort), 38 | InetSocketAddress.createUnresolved(localHost, localPort)); 39 | } 40 | 41 | public SimpleUdpTransport(final String host) throws IOException { 42 | this(host, DEFAULT_PORT); 43 | } 44 | 45 | public SimpleUdpTransport(final String remoteHost, final String localHost) throws IOException { 46 | this(remoteHost, DEFAULT_PORT, localHost, 0); 47 | } 48 | 49 | public SimpleUdpTransport(final int port) throws IOException { 50 | this(InetAddress.getLocalHost().getHostAddress(), port); 51 | } 52 | 53 | @Override 54 | public Msg sendMessage(final Msg msg) throws IOException { 55 | final byte[] body = msg.toByteArray(); 56 | final DatagramPacket packet = new DatagramPacket(body, body.length, ensureResolved(remoteAddress)); 57 | socket.send(packet); 58 | 59 | return null; 60 | } 61 | 62 | @Override 63 | public boolean isConnected() { 64 | return connected; 65 | } 66 | 67 | @Override 68 | public synchronized void connect() throws IOException { 69 | if (this.localAddress != null){ 70 | socket = new DatagramSocket(ensureResolved(localAddress)); 71 | }else{ 72 | socket = new DatagramSocket(); 73 | } 74 | connected = true; 75 | } 76 | 77 | @Override 78 | public synchronized void close() { 79 | try { 80 | socket.close(); 81 | } finally { 82 | socket = null; 83 | connected = false; 84 | } 85 | } 86 | 87 | @Override 88 | public void reconnect() throws IOException { 89 | close(); 90 | connect(); 91 | } 92 | 93 | @Override 94 | public void flush() throws IOException { 95 | // Noop 96 | } 97 | 98 | @Override 99 | public Transport transport() { 100 | return null; 101 | } 102 | 103 | private static InetSocketAddress ensureResolved(InetSocketAddress maybeUnresolved) 104 | throws UnknownHostException { 105 | if (maybeUnresolved.isUnresolved()) { 106 | return new InetSocketAddress( 107 | InetAddress.getByName(maybeUnresolved.getHostName()), maybeUnresolved.getPort()); 108 | } else { 109 | return maybeUnresolved; 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /riemann-java-client/src/main/java/io/riemann/riemann/client/SynchronousTransport.java: -------------------------------------------------------------------------------- 1 | package io.riemann.riemann.client; 2 | 3 | import io.riemann.riemann.Proto.Msg; 4 | import java.io.IOException; 5 | 6 | public interface SynchronousTransport extends Transport { 7 | // Sends a message and waits for its response, if possible. If the underlying 8 | // transport doesn't support receiving a response, returns null. 9 | public Msg sendMessage(final Msg msg) throws IOException; 10 | } 11 | -------------------------------------------------------------------------------- /riemann-java-client/src/main/java/io/riemann/riemann/client/TcpHandler.java: -------------------------------------------------------------------------------- 1 | package io.riemann.riemann.client; 2 | 3 | import io.netty.channel.ChannelHandlerContext; 4 | import io.netty.channel.ChannelOutboundHandlerAdapter; 5 | import io.netty.channel.ChannelPromise; 6 | import io.netty.channel.CombinedChannelDuplexHandler; 7 | import io.netty.channel.SimpleChannelInboundHandler; 8 | import io.netty.util.concurrent.Future; 9 | import io.netty.util.concurrent.GenericFutureListener; 10 | import io.riemann.riemann.Proto.Msg; 11 | import java.io.IOException; 12 | 13 | public class TcpHandler 14 | extends CombinedChannelDuplexHandler { 15 | 16 | public final WriteQueue queue = new WriteQueue(); 17 | public final ExceptionReporter exceptionReporter; 18 | // The last error used to fulfill outstanding promises. 19 | public volatile IOException lastError = 20 | new IOException("Channel closed."); 21 | 22 | public TcpHandler(final ExceptionReporter exceptionReporter) { 23 | this.exceptionReporter = exceptionReporter; 24 | init(new Inbound(), new Outbound()); 25 | } 26 | 27 | public class Inbound extends SimpleChannelInboundHandler { 28 | @Override 29 | protected void channelRead0(ChannelHandlerContext ctx, Msg msg) { 30 | // When messages are received, deliver them to the next queued promise. 31 | queue.take().deliver(msg); 32 | } 33 | 34 | @Override 35 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 36 | // Log exceptions and close. 37 | try { 38 | exceptionReporter.reportException(cause); 39 | } catch (final Exception ee) { 40 | // Oh well 41 | } 42 | 43 | queue.close(cause); 44 | ctx.channel().close(); 45 | super.exceptionCaught(ctx, cause); 46 | } 47 | } 48 | 49 | public class Outbound extends ChannelOutboundHandlerAdapter { 50 | @Override 51 | public void write(ChannelHandlerContext ctx, Object msg, final ChannelPromise channelPromise) 52 | throws Exception { 53 | // Destructure the write 54 | final Write write = (Write) msg; 55 | final Promise promise = write.promise; 56 | 57 | channelPromise.addListener( 58 | new GenericFutureListener>() { 59 | @Override 60 | public void operationComplete(Future future) throws Exception { 61 | if (future.isSuccess()) { 62 | queue.put(promise); 63 | } else if (future.cause() != null) { 64 | promise.deliver(new IOException("Write failed.", future.cause())); 65 | } else { 66 | promise.deliver(new IOException("Write failed.")); 67 | } 68 | } 69 | }); 70 | super.write(ctx, ((Write) msg).message, channelPromise); 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /riemann-java-client/src/main/java/io/riemann/riemann/client/TcpTransport.java: -------------------------------------------------------------------------------- 1 | package io.riemann.riemann.client; 2 | 3 | import io.netty.bootstrap.Bootstrap; 4 | import io.netty.channel.Channel; 5 | import io.netty.channel.ChannelFuture; 6 | import io.netty.channel.ChannelFutureListener; 7 | import io.netty.channel.ChannelInitializer; 8 | import io.netty.channel.ChannelOption; 9 | import io.netty.channel.ChannelPipeline; 10 | import io.netty.channel.EventLoopGroup; 11 | import io.netty.channel.group.ChannelGroup; 12 | import io.netty.channel.group.DefaultChannelGroup; 13 | import io.netty.channel.nio.NioEventLoopGroup; 14 | import io.netty.channel.socket.SocketChannel; 15 | import io.netty.channel.socket.nio.NioSocketChannel; 16 | import io.netty.handler.codec.LengthFieldBasedFrameDecoder; 17 | import io.netty.handler.codec.LengthFieldPrepender; 18 | import io.netty.handler.codec.protobuf.ProtobufDecoder; 19 | import io.netty.handler.codec.protobuf.ProtobufEncoder; 20 | import io.netty.handler.ssl.SslHandler; 21 | import io.netty.util.concurrent.GlobalEventExecutor; 22 | import io.riemann.riemann.Proto.Msg; 23 | import java.io.IOException; 24 | import java.net.InetAddress; 25 | import java.net.InetSocketAddress; 26 | import java.util.concurrent.Semaphore; 27 | import java.util.concurrent.TimeUnit; 28 | import java.util.concurrent.atomic.AtomicBoolean; 29 | import java.util.concurrent.atomic.AtomicInteger; 30 | import java.util.concurrent.atomic.AtomicLong; 31 | import java.util.concurrent.atomic.AtomicReference; 32 | import javax.net.ssl.SSLContext; 33 | import javax.net.ssl.SSLEngine; 34 | import org.slf4j.Logger; 35 | import org.slf4j.LoggerFactory; 36 | 37 | public class TcpTransport implements AsynchronousTransport { 38 | // Logger 39 | public final Logger logger = LoggerFactory.getLogger(TcpTransport.class); 40 | 41 | // Shared pipeline handlers 42 | public static final ProtobufDecoder pbDecoder = 43 | new ProtobufDecoder(Msg.getDefaultInstance()); 44 | public static final ProtobufEncoder pbEncoder = 45 | new ProtobufEncoder(); 46 | public static final LengthFieldPrepender frameEncoder = 47 | new LengthFieldPrepender(4); 48 | 49 | public static final int DEFAULT_PORT = 5555; 50 | 51 | // I AM A STATE MUSHEEN 52 | public enum State { 53 | DISCONNECTED, 54 | CONNECTING, 55 | CONNECTED, 56 | DISCONNECTING 57 | } 58 | 59 | // STATE STATE STATE 60 | public volatile State state = State.DISCONNECTED; 61 | public final EventLoopGroup eventLoopGroup = new NioEventLoopGroup(1); 62 | public final ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); 63 | public volatile Bootstrap bootstrap; 64 | public volatile Semaphore writeLimiter = new Semaphore(8192); 65 | 66 | // Configuration 67 | public final AtomicBoolean autoFlush = new AtomicBoolean(true); 68 | public final AtomicInteger writeLimit = new AtomicInteger(8192); 69 | public final AtomicLong reconnectDelay = new AtomicLong(5000); 70 | public final AtomicInteger connectTimeout = new AtomicInteger(5000); 71 | public final AtomicInteger writeTimeout = new AtomicInteger(5000); 72 | public final AtomicInteger writeBufferHigh = new AtomicInteger(1024 * 64); 73 | public final AtomicInteger writeBufferLow = new AtomicInteger(1024 * 8); 74 | 75 | public final InetSocketAddress remoteAddress; 76 | public final InetSocketAddress localAddress; 77 | public final AtomicReference sslContext = 78 | new AtomicReference(); 79 | 80 | public volatile ExceptionReporter exceptionReporter = new ExceptionReporter() { 81 | public void reportException(final Throwable t) { 82 | // By default, don't spam the logs. 83 | } 84 | }; 85 | 86 | public void setExceptionReporter(final ExceptionReporter exceptionReporter) { 87 | this.exceptionReporter = exceptionReporter; 88 | } 89 | 90 | public TcpTransport(final InetSocketAddress remoteAddress) { 91 | this.remoteAddress = remoteAddress; 92 | this.localAddress = null; 93 | } 94 | 95 | public TcpTransport(final InetSocketAddress remoteAddress, final InetSocketAddress localAddress) { 96 | this.remoteAddress = remoteAddress; 97 | this.localAddress = localAddress; 98 | } 99 | 100 | public TcpTransport(final String remoteHost, final int remotePort) throws IOException { 101 | this(InetSocketAddress.createUnresolved(remoteHost, remotePort)); 102 | } 103 | 104 | public TcpTransport( 105 | final String remoteHost, final int remotePort, final String localHost, final int localPort) 106 | throws IOException { 107 | this( 108 | InetSocketAddress.createUnresolved(remoteHost, remotePort), InetSocketAddress.createUnresolved(localHost, localPort)); 109 | } 110 | 111 | public TcpTransport(final String remoteHost) throws IOException { 112 | this(remoteHost, DEFAULT_PORT); 113 | } 114 | 115 | public TcpTransport(final String remoteHost, final String localHost) throws IOException { 116 | this(remoteHost, DEFAULT_PORT, localHost, 0); 117 | } 118 | 119 | public TcpTransport(final int remotePort) throws IOException { 120 | this(InetAddress.getLocalHost().getHostAddress(), remotePort); 121 | } 122 | 123 | // Set the number of outstanding writes allowed at any time. 124 | public synchronized TcpTransport setWriteBufferLimit(final int limit) { 125 | if (isConnected()) { 126 | throw new IllegalStateException("can't modify the write buffer limit of a connected transport; please set the limit before connecting"); 127 | } 128 | 129 | writeLimit.set(limit); 130 | writeLimiter = new Semaphore(limit); 131 | return this; 132 | } 133 | 134 | @Override 135 | public boolean isConnected() { 136 | // Are we in state connected? 137 | if (state != State.CONNECTED) { 138 | return false; 139 | } 140 | 141 | // Is at least one channel connected? 142 | for (Channel ch : channels) { 143 | if (ch.isOpen()) { 144 | return true; 145 | } 146 | } 147 | 148 | return false; 149 | } 150 | 151 | // Builds a new SSLHandler 152 | public SslHandler sslHandler() { 153 | final SSLContext context = sslContext.get(); 154 | if (context == null) { 155 | return null; 156 | } 157 | 158 | final SSLEngine engine = context.createSSLEngine(); 159 | engine.setUseClientMode(true); 160 | 161 | final SslHandler handler = new SslHandler(engine); 162 | 163 | // to disable tls renegotiation see: 164 | // https://stackoverflow.com/questions/31418644/is-it-possible-to-disable-tls-renegotiation-in-netty-4 165 | 166 | return handler; 167 | } 168 | 169 | @Override 170 | // Does nothing if not currently disconnected. 171 | public synchronized void connect() throws IOException { 172 | if (state != State.DISCONNECTED) { 173 | return; 174 | } 175 | state = State.CONNECTING; 176 | 177 | // Create bootstrap 178 | bootstrap = new Bootstrap().group(eventLoopGroup) 179 | .localAddress(localAddress) 180 | .remoteAddress(remoteAddress) 181 | .channel(NioSocketChannel.class) 182 | .handler( 183 | new ChannelInitializer() { 184 | @Override 185 | protected void initChannel(SocketChannel channel) { 186 | ChannelPipeline p = channel.pipeline(); 187 | // Reconnections 188 | p.addLast( 189 | "reconnect", 190 | new ReconnectHandler(bootstrap, channels, reconnectDelay, TimeUnit.MILLISECONDS)); 191 | 192 | // TLS 193 | final SslHandler sslHandler = sslHandler(); 194 | if (sslHandler != null) { 195 | p.addLast("tls", sslHandler); 196 | } 197 | 198 | // Normal codec 199 | p.addLast("frame-decoder", new LengthFieldBasedFrameDecoder( 200 | Integer.MAX_VALUE, 0, 4, 0, 4)); 201 | p.addLast("frame-encoder", frameEncoder); 202 | p.addLast("protobuf-decoder", pbDecoder); 203 | p.addLast("protobuf-encoder", pbEncoder); 204 | p.addLast("handler", new TcpHandler(exceptionReporter)); 205 | }}); 206 | 207 | // Set bootstrap options 208 | bootstrap.option(ChannelOption.TCP_NODELAY, true); 209 | bootstrap.option(ChannelOption.SO_KEEPALIVE, true); 210 | bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, connectTimeout.get()); 211 | bootstrap.option(ChannelOption.WRITE_BUFFER_LOW_WATER_MARK, writeBufferLow.get()); 212 | bootstrap.option(ChannelOption.WRITE_BUFFER_HIGH_WATER_MARK, writeBufferHigh.get()); 213 | bootstrap.localAddress(localAddress); 214 | bootstrap.remoteAddress(remoteAddress); 215 | 216 | // Connect and wait for connection ready 217 | final ChannelFuture result = bootstrap.connect(); 218 | channels.add(result.channel()); 219 | result.awaitUninterruptibly(); 220 | 221 | // At this point we consider the client "connected"--even though the 222 | // connection may have failed. The channel will continue to initiate 223 | // reconnect attempts in the background. 224 | state = State.CONNECTED; 225 | 226 | // We'll throw an exception so users can pretend this call is synchronous 227 | // (and log errors as appropriate) but the client might succeed later. 228 | if (! result.isSuccess()) { 229 | throw new IOException("Connection failed", result.cause()); 230 | } 231 | } 232 | 233 | @Override 234 | public void close() { 235 | close(false); 236 | } 237 | 238 | public synchronized void close(boolean force) { 239 | if (!(force || state == State.CONNECTED)) { 240 | return; 241 | } 242 | 243 | try { 244 | channels.close().awaitUninterruptibly(); 245 | eventLoopGroup.shutdownGracefully().awaitUninterruptibly(); 246 | } finally { 247 | bootstrap = null; 248 | state = State.DISCONNECTED; 249 | } 250 | } 251 | 252 | @Override 253 | public synchronized void reconnect() throws IOException { 254 | close(); 255 | connect(); 256 | } 257 | 258 | @Override 259 | public void flush() throws IOException { 260 | channels.flush(); 261 | } 262 | 263 | // Write a message to any handler and return a promise to be fulfilled by 264 | // the corresponding response Msg. 265 | @Override 266 | public IPromise sendMessage(final Msg msg) { 267 | return sendMessage(msg, new Promise()); 268 | } 269 | 270 | // Write a message to any available handler, fulfilling a specific promise. 271 | public Promise sendMessage(final Msg msg, final Promise promise) { 272 | if (state != State.CONNECTED) { 273 | promise.deliver(new IOException("client not connected")); 274 | return promise; 275 | } 276 | 277 | final Write write = new Write(msg, promise); 278 | final Semaphore limiter = writeLimiter; 279 | 280 | // Reserve a slot in the queue 281 | if (limiter.tryAcquire()) { 282 | for (Channel channel : channels) { 283 | // When the write is flushed from our local buffer, release our 284 | // limiter permit. 285 | ChannelFuture f; 286 | if (autoFlush.get()) { 287 | f = channel.writeAndFlush(write); 288 | } else { 289 | f = channel.write(write); 290 | } 291 | f.addListener( 292 | new ChannelFutureListener() { 293 | @Override 294 | public void operationComplete(ChannelFuture f) { 295 | limiter.release(); 296 | } 297 | }); 298 | return promise; 299 | } 300 | 301 | // No channels available, release the slot. 302 | limiter.release(); 303 | promise.deliver(new IOException("no channels available")); 304 | return promise; 305 | } 306 | 307 | // Buffer's full. 308 | promise.deliver( 309 | new OverloadedException( 310 | "client write buffer is full: " 311 | + writeLimiter.availablePermits() 312 | + " / " 313 | + writeLimit.get() 314 | + " messages.")); 315 | return promise; 316 | } 317 | 318 | @Override 319 | public Transport transport() { 320 | return null; 321 | } 322 | } 323 | -------------------------------------------------------------------------------- /riemann-java-client/src/main/java/io/riemann/riemann/client/Transport.java: -------------------------------------------------------------------------------- 1 | package io.riemann.riemann.client; 2 | 3 | import java.io.IOException; 4 | import java.lang.AutoCloseable; 5 | 6 | // A common transport interface. 7 | public interface Transport extends AutoCloseable { 8 | boolean isConnected(); 9 | void connect() throws IOException; 10 | 11 | void reconnect() throws IOException; 12 | 13 | void flush() throws IOException; 14 | 15 | // Our close should never throw. 16 | void close(); 17 | 18 | Transport transport(); 19 | } 20 | -------------------------------------------------------------------------------- /riemann-java-client/src/main/java/io/riemann/riemann/client/UdpTransport.java: -------------------------------------------------------------------------------- 1 | package io.riemann.riemann.client; 2 | 3 | import io.netty.bootstrap.Bootstrap; 4 | import io.netty.channel.Channel; 5 | import io.netty.channel.ChannelFuture; 6 | import io.netty.channel.ChannelHandlerContext; 7 | import io.netty.channel.ChannelInboundHandlerAdapter; 8 | import io.netty.channel.ChannelInitializer; 9 | import io.netty.channel.ChannelOption; 10 | import io.netty.channel.ChannelPipeline; 11 | import io.netty.channel.DefaultEventLoopGroup; 12 | import io.netty.channel.EventLoopGroup; 13 | import io.netty.channel.group.ChannelGroup; 14 | import io.netty.channel.group.DefaultChannelGroup; 15 | import io.netty.channel.nio.NioEventLoopGroup; 16 | import io.netty.channel.socket.nio.NioDatagramChannel; 17 | import io.netty.handler.codec.protobuf.ProtobufEncoder; 18 | import io.netty.util.concurrent.GlobalEventExecutor; 19 | import io.riemann.riemann.Proto.Msg; 20 | import java.io.IOException; 21 | import java.net.InetAddress; 22 | import java.net.InetSocketAddress; 23 | import java.util.concurrent.TimeUnit; 24 | import java.util.concurrent.atomic.AtomicBoolean; 25 | import java.util.concurrent.atomic.AtomicInteger; 26 | import java.util.concurrent.atomic.AtomicLong; 27 | 28 | public class UdpTransport implements SynchronousTransport { 29 | // For writes we don't care about 30 | public static final Promise blackhole = 31 | new Promise(); 32 | 33 | // Shared pipeline handlers 34 | public static final ProtobufEncoder pbEncoder = new ProtobufEncoder(); 35 | public final DiscardHandler discardHandler = new DiscardHandler(); 36 | 37 | public static final int DEFAULT_PORT = 5555; 38 | 39 | // I AM A STATE MUSHEEN 40 | public enum State { 41 | DISCONNECTED, 42 | CONNECTING, 43 | CONNECTED, 44 | DISCONNECTING 45 | } 46 | 47 | // STATE STATE STATE 48 | public volatile State state = State.DISCONNECTED; 49 | public volatile Bootstrap bootstrap; 50 | public final EventLoopGroup eventLoopGroup = new NioEventLoopGroup(); 51 | public final ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); 52 | 53 | // Configuration 54 | public final AtomicLong reconnectDelay = new AtomicLong(5000); 55 | public final AtomicLong connectTimeout = new AtomicLong(5000); 56 | // Changes to this value are applied only on reconnect. 57 | public final AtomicInteger sendBufferSize = new AtomicInteger(16384); 58 | public final AtomicBoolean autoFlush = new AtomicBoolean(true); 59 | 60 | public final InetSocketAddress remoteAddress; 61 | public final InetSocketAddress localAddress; 62 | 63 | public volatile ExceptionReporter exceptionReporter = new ExceptionReporter() { 64 | @Override 65 | public void reportException(final Throwable t) { 66 | t.printStackTrace(); 67 | } 68 | }; 69 | 70 | public void setExceptionReporter(final ExceptionReporter exceptionReporter) { 71 | this.exceptionReporter = exceptionReporter; 72 | } 73 | 74 | public UdpTransport(final InetSocketAddress remoteAddress) { 75 | this.remoteAddress = remoteAddress; 76 | this.localAddress = null; 77 | } 78 | 79 | public UdpTransport(final InetSocketAddress remoteAddress,final InetSocketAddress localAddress) { 80 | this.remoteAddress = remoteAddress; 81 | this.localAddress = localAddress; 82 | } 83 | 84 | public UdpTransport(final String host, final int port) throws IOException { 85 | this(new InetSocketAddress(host, port)); 86 | } 87 | 88 | public UdpTransport(final String remoteHost, final int remotePort, final String localHost, final int localPort) throws IOException { 89 | this(new InetSocketAddress(remoteHost, remotePort),new InetSocketAddress(localHost, localPort) ); 90 | } 91 | 92 | public UdpTransport(final String remoteHost) throws IOException { 93 | this(remoteHost, DEFAULT_PORT); 94 | } 95 | 96 | public UdpTransport(final String remoteHost, final String localHost) throws IOException { 97 | this(remoteHost, DEFAULT_PORT, localHost, 0); 98 | } 99 | 100 | public UdpTransport(final int port) throws IOException { 101 | this(InetAddress.getLocalHost().getHostAddress(), port); 102 | } 103 | 104 | @Override 105 | public boolean isConnected() { 106 | // Are we in state connected? 107 | return state == State.CONNECTED; 108 | } 109 | 110 | @Override 111 | // Does nothing if not currently disconnected. 112 | public synchronized void connect() throws IOException { 113 | if (state != State.DISCONNECTED) { 114 | return; 115 | }; 116 | state = State.CONNECTING; 117 | 118 | // Create bootstrap 119 | bootstrap = new Bootstrap().group(eventLoopGroup) 120 | .channel(NioDatagramChannel.class); 121 | 122 | // Set up pipeline factory. 123 | bootstrap.handler( 124 | new ChannelInitializer() { 125 | @Override 126 | protected void initChannel(Channel ch) throws Exception { 127 | ChannelPipeline p = ch.pipeline(); 128 | p.addLast("reconnect", new ReconnectHandler( 129 | bootstrap, 130 | channels, 131 | reconnectDelay, 132 | TimeUnit.MILLISECONDS)); 133 | p.addLast("protobuf-encoder", pbEncoder); 134 | p.addLast("discard", discardHandler); 135 | } 136 | } 137 | ); 138 | 139 | // Set bootstrap options 140 | bootstrap.remoteAddress(remoteAddress); 141 | bootstrap.localAddress(localAddress); 142 | bootstrap.option(ChannelOption.SO_SNDBUF, sendBufferSize.get()); 143 | 144 | // Connect 145 | final ChannelFuture result = bootstrap.connect(); 146 | channels.add(result.channel()); 147 | result.awaitUninterruptibly(); 148 | 149 | // Check for errors. 150 | if (! result.isSuccess()) { 151 | close(true); 152 | throw new IOException("Connection failed", result.cause()); 153 | } 154 | 155 | // Done 156 | state = State.CONNECTED; 157 | } 158 | 159 | @Override 160 | public void close() { 161 | close(false); 162 | } 163 | 164 | public synchronized void close(boolean force) { 165 | if (!(force || state == State.CONNECTED)) { 166 | return; 167 | } 168 | // Close channel 169 | try { 170 | channels.close().awaitUninterruptibly(); 171 | } finally { 172 | 173 | // Stop bootstrap 174 | try { 175 | eventLoopGroup.shutdownGracefully(); 176 | } finally { 177 | bootstrap = null; 178 | state = State.DISCONNECTED; 179 | } 180 | } 181 | } 182 | 183 | @Override 184 | public void reconnect() throws IOException { 185 | close(); 186 | connect(); 187 | } 188 | 189 | // An Noop 190 | @Override 191 | public void flush() throws IOException { 192 | channels.flush(); 193 | } 194 | 195 | @Override 196 | public Msg sendMessage(final Msg msg) { 197 | if (autoFlush.get()) { 198 | channels.writeAndFlush(msg); 199 | } else { 200 | channels.write(msg); 201 | } 202 | 203 | return null; 204 | } 205 | 206 | @Override 207 | public Transport transport() { 208 | return null; 209 | } 210 | 211 | public class DiscardHandler extends ChannelInboundHandlerAdapter { 212 | 213 | @Override 214 | public void channelActive(ChannelHandlerContext ctx) { 215 | ctx.channel().config().setAutoRead(false); 216 | } 217 | 218 | @Override 219 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { 220 | try { 221 | exceptionReporter.reportException(cause); 222 | } catch (final Exception ee) { 223 | // Oh well 224 | } finally { 225 | try { 226 | ctx.channel().close(); 227 | } catch (final Exception ee) { 228 | exceptionReporter.reportException(ee); 229 | } 230 | } 231 | } 232 | } 233 | } 234 | -------------------------------------------------------------------------------- /riemann-java-client/src/main/java/io/riemann/riemann/client/UnsupportedJVMException.java: -------------------------------------------------------------------------------- 1 | package io.riemann.riemann.client; 2 | 3 | // Throw when we can't find an implementation of a particular class 4 | public class UnsupportedJVMException extends Exception { 5 | } 6 | -------------------------------------------------------------------------------- /riemann-java-client/src/main/java/io/riemann/riemann/client/Write.java: -------------------------------------------------------------------------------- 1 | package io.riemann.riemann.client; 2 | 3 | import io.riemann.riemann.Proto.Msg; 4 | 5 | public class Write { 6 | public final Msg message; 7 | public final Promise promise; 8 | 9 | public Write(final Msg message, final Promise promise) { 10 | this.message = message; 11 | this.promise = promise; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /riemann-java-client/src/main/java/io/riemann/riemann/client/WriteQueue.java: -------------------------------------------------------------------------------- 1 | package io.riemann.riemann.client; 2 | 3 | import io.riemann.riemann.Proto.Msg; 4 | import java.io.*; 5 | import java.util.concurrent.LinkedBlockingQueue; 6 | 7 | // A synchronized FIFO queue intended to track the outstanding writes 8 | // associated with a TCP connection. Calling close() flushes the queue 9 | // atomically and prevents future writes. Open() allows writes to be enqueued 10 | // again. 11 | public class WriteQueue { 12 | public boolean isOpen = true; 13 | public volatile int size = 0; 14 | public final LinkedBlockingQueue> queue = 15 | new LinkedBlockingQueue>(); 16 | 17 | public synchronized void open() { 18 | isOpen = true; 19 | size = 0; 20 | } 21 | 22 | // Returns the number of promises cleared. 23 | public synchronized void close(Throwable t) { 24 | isOpen = false; 25 | 26 | // Deliver exceptions to all outstanding promises. 27 | final IOException ex = new IOException("channel closed", t); 28 | Promise promise; 29 | while ((promise = queue.poll()) != null) { 30 | promise.deliver(ex); 31 | } 32 | 33 | size = 0; 34 | } 35 | 36 | public int size() { 37 | return this.size; 38 | } 39 | 40 | public synchronized void put(final Promise p) throws InterruptedException { 41 | if (isOpen) { 42 | try { 43 | queue.put(p); 44 | size++; 45 | } catch (RuntimeException e) { 46 | size = queue.size(); 47 | throw e; 48 | } catch (InterruptedException e) { 49 | size = queue.size(); 50 | throw e; 51 | } 52 | } else { 53 | p.deliver(new IOException("Channel closed.")); 54 | } 55 | } 56 | 57 | public synchronized Promise take() { 58 | try { 59 | final Promise p = queue.take(); 60 | size--; 61 | return p; 62 | } catch (RuntimeException e) { 63 | size = queue.size(); 64 | throw e; 65 | } catch (InterruptedException e) { 66 | size = queue.size(); 67 | throw new RuntimeException(e); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /riemann-java-client/src/main/proto/riemann/proto.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto2"; 2 | 3 | option java_package = "io.riemann.riemann"; 4 | option java_outer_classname = "Proto"; 5 | 6 | // Deprecated; state was used by early versions of the protocol, but not any 7 | // more. 8 | message State { 9 | optional int64 time = 1; 10 | optional string state = 2; 11 | optional string service = 3; 12 | optional string host = 4; 13 | optional string description = 5; 14 | optional bool once = 6; 15 | repeated string tags = 7; 16 | optional float ttl = 8; 17 | } 18 | 19 | message Event { 20 | optional int64 time = 1; 21 | optional string state = 2; 22 | optional string service = 3; 23 | optional string host = 4; 24 | optional string description = 5; 25 | repeated string tags = 7; 26 | optional float ttl = 8; 27 | repeated Attribute attributes = 9; 28 | 29 | optional int64 time_micros = 10; 30 | optional sint64 metric_sint64 = 13; 31 | optional double metric_d = 14; 32 | optional float metric_f = 15; 33 | } 34 | 35 | message Query { 36 | optional string string = 1; 37 | } 38 | 39 | message Msg { 40 | optional bool ok = 2; 41 | optional string error = 3; 42 | repeated State states = 4; 43 | optional Query query = 5; 44 | repeated Event events = 6; 45 | } 46 | 47 | message Attribute { 48 | required string key = 1; 49 | optional string value = 2; 50 | } 51 | -------------------------------------------------------------------------------- /riemann-java-client/src/test/java/riemann/java/client/tests/BatchClientPromiseTest.java: -------------------------------------------------------------------------------- 1 | package riemann.java.client.tests; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertTrue; 5 | import org.junit.Test; 6 | 7 | import java.io.IOException; 8 | import java.util.*; 9 | import java.util.concurrent.TimeUnit; 10 | 11 | import io.riemann.riemann.client.IPromise; 12 | import io.riemann.riemann.client.IRiemannClient; 13 | import io.riemann.riemann.client.RiemannBatchClient; 14 | import io.riemann.riemann.client.RiemannClient; 15 | import io.riemann.riemann.client.ServerError; 16 | import io.riemann.riemann.client.UnsupportedJVMException; 17 | 18 | import io.riemann.riemann.Proto.Event; 19 | import io.riemann.riemann.Proto.Msg; 20 | 21 | public class BatchClientPromiseTest { 22 | @Test 23 | public void sendEventsTest() throws Exception, IOException, InterruptedException, ServerError, UnsupportedJVMException { 24 | final ArrayList events = new ArrayList(); 25 | final ArrayList> promises = new ArrayList>(); 26 | 27 | final Server server = new OkServer(); 28 | IRiemannClient client = null; 29 | 30 | final int BATCH_SIZE = 10; 31 | final int NUM_EVENTS = 15; 32 | try { 33 | client = new RiemannBatchClient(RiemannClient.tcp(server.start()), 34 | BATCH_SIZE); 35 | client.connect(); 36 | { 37 | final Event e = Util.createEvent(); 38 | events.add(e); 39 | IPromise promise = client.sendEvent(e); 40 | // First event should be sitting in the buffer, not sent yet. 41 | assertEquals(null, promise.deref(10, (Object) null)); 42 | promises.add(promise); 43 | } 44 | for (int i = 1; i < NUM_EVENTS; i++) { 45 | final Event e = Util.createEvent(); 46 | events.add(e); 47 | promises.add(client.sendEvent(e)); 48 | } 49 | client.flush(); 50 | for (int i = 0; i < events.size(); i++) { 51 | Msg rsp = promises.get(i).deref(100, TimeUnit.MILLISECONDS, 52 | Msg.newBuilder().setOk(false).build()); 53 | assertTrue(!rsp.hasOk() || rsp.getOk()); 54 | } 55 | for (int i = 0; i < events.size(); ) { 56 | final int expecting = Math.min(events.size() - i, BATCH_SIZE); 57 | Msg recv = server.received.poll(); 58 | assertEquals(expecting, recv.getEventsCount()); 59 | for (int j = 0; j < expecting; i++, j++) { 60 | assertEquals(events.get(i), recv.getEvents(j)); 61 | } 62 | } 63 | } finally { 64 | if (client != null) { 65 | client.close(); 66 | } 67 | server.stop(); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /riemann-java-client/src/test/java/riemann/java/client/tests/ChainPromiseTest.java: -------------------------------------------------------------------------------- 1 | package riemann.java.client.tests; 2 | 3 | import io.riemann.riemann.client.ChainPromise; 4 | import io.riemann.riemann.client.Promise; 5 | import java.lang.Runnable; 6 | import java.lang.Thread; 7 | import org.junit.Before; 8 | import org.junit.Test; 9 | import static org.junit.Assert.assertEquals; 10 | import java.io.IOException; 11 | 12 | public class ChainPromiseTest { 13 | 14 | private ChainPromise cp; 15 | 16 | @Before 17 | public void setUp() { 18 | cp = new ChainPromise(); 19 | } 20 | 21 | @Test 22 | public void singleTest() throws IOException { 23 | // A previous version of ChainPromise didn't properly attach the 24 | // inner promise, this problem was visible in 25 | // BatchClientPromiseTest 26 | Promise p = new Promise(); 27 | cp.attach(p); 28 | p.deliver("foo"); 29 | assertEquals("foo", cp.deref(10, (Object) null)); 30 | } 31 | 32 | @Test 33 | public void threadTest() throws IOException { 34 | final Promise p = new Promise(); 35 | new Thread(new Runnable() { 36 | public void run() { 37 | try { 38 | Thread.sleep(100); 39 | } catch (InterruptedException e) { 40 | System.out.println("interrupted"); 41 | } 42 | p.deliver("bar"); 43 | } 44 | }).start(); 45 | new Thread(new Runnable() { 46 | public void run() { 47 | try { 48 | Thread.sleep(100); 49 | } catch (InterruptedException e) { 50 | System.out.println("interrupted"); 51 | } 52 | cp.attach(p); 53 | } 54 | }).start(); 55 | assertEquals("bar", p.deref(200, (Object) null)); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /riemann-java-client/src/test/java/riemann/java/client/tests/EchoServer.java: -------------------------------------------------------------------------------- 1 | package riemann.java.client.tests; 2 | 3 | import io.riemann.riemann.Proto.Msg; 4 | 5 | public class EchoServer extends Server { 6 | final long delay; 7 | 8 | public EchoServer() { 9 | this(0); 10 | } 11 | 12 | public EchoServer(final long delay) { 13 | this.delay = delay; 14 | } 15 | 16 | public Msg handle(final Msg m) { 17 | if (0 < delay) { 18 | try { 19 | Thread.sleep(delay); 20 | } catch (InterruptedException e) { 21 | } 22 | } 23 | //if (0 < m.getEventsCount()) { 24 | // System.out.println("Server: " + m.getEvents(0).getMetricSint64()); 25 | //} 26 | return m; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /riemann-java-client/src/test/java/riemann/java/client/tests/EventBuilderTest.java: -------------------------------------------------------------------------------- 1 | package riemann.java.client.tests; 2 | 3 | import com.google.protobuf.ProtocolStringList; 4 | import io.riemann.riemann.Proto; 5 | import io.riemann.riemann.client.EventBuilder; 6 | import org.junit.Test; 7 | 8 | import java.util.*; 9 | 10 | import static org.junit.Assert.assertEquals; 11 | 12 | public class EventBuilderTest { 13 | 14 | @Test 15 | public void should_create_event() { 16 | Map attributes = new HashMap(); 17 | attributes.put("network_zone", "dmz"); 18 | List tags = new ArrayList(); 19 | tags.add("fast"); 20 | Proto.Event event = new EventBuilder() 21 | .host("riemann.localhost") 22 | .metric(42.2) 23 | .service("cpu_percent_usage") 24 | .state("fine") 25 | .tags("high", "backend") 26 | .tag("storage") 27 | .tags(tags) 28 | .description("Processor usage") 29 | .attribute("health", "good") 30 | .attribute("environment", "production") 31 | .attribute("datacenter", "eu_west") 32 | .attributes(attributes) 33 | .ttl(60) 34 | .build(); 35 | 36 | assertEquals("cpu_percent_usage", event.getService()); 37 | assertEquals("riemann.localhost", event.getHost()); 38 | assertEquals("fine", event.getState()); 39 | assertEquals(42.2, event.getMetricD(), 0D); 40 | ProtocolStringList eventTags = event.getTagsList(); 41 | assertEquals(4, eventTags.size()); 42 | assertEquals("high", eventTags.get(0)); 43 | assertEquals("backend", eventTags.get(1)); 44 | assertEquals("storage", eventTags.get(2)); 45 | assertEquals("fast", eventTags.get(3)); 46 | assertEquals("Processor usage", event.getDescription()); 47 | List eventAttributes = getAttributesSortedByKey(event); 48 | assertEquals(4, eventAttributes.size()); 49 | assertEqualsAttribute("datacenter", "eu_west", eventAttributes.get(0)); 50 | assertEqualsAttribute("environment", "production", eventAttributes.get(1)); 51 | assertEqualsAttribute("health", "good", eventAttributes.get(2)); 52 | assertEqualsAttribute("network_zone", "dmz", eventAttributes.get(3)); 53 | assertEquals(60F, event.getTtl(), 0F); 54 | } 55 | 56 | @Test 57 | public void should_create_event_with_metric_float() { 58 | Proto.Event event = new EventBuilder() 59 | .metric(123.4F) 60 | .build(); 61 | 62 | assertEquals(123.4F, event.getMetricF(), 0F); 63 | } 64 | 65 | @Test 66 | public void should_create_event_with_metric_int() { 67 | Proto.Event event = new EventBuilder() 68 | .metric(123) 69 | .build(); 70 | 71 | assertEquals(123, event.getMetricSint64()); 72 | } 73 | 74 | @Test 75 | public void should_create_event_with_metric_short() { 76 | Proto.Event event = new EventBuilder() 77 | .metric((short) 1) 78 | .build(); 79 | 80 | assertEquals(1, event.getMetricSint64()); 81 | } 82 | 83 | @Test 84 | public void should_create_event_with_metric_long() { 85 | Proto.Event event = new EventBuilder() 86 | .metric(1234567891011L) 87 | .build(); 88 | 89 | assertEquals(1234567891011L, event.getMetricSint64()); 90 | } 91 | 92 | @Test 93 | public void should_create_event_with_metric_byte() { 94 | Proto.Event event = new EventBuilder() 95 | .metric((byte) 1) 96 | .build(); 97 | 98 | assertEquals(1, event.getMetricSint64()); 99 | } 100 | 101 | @Test 102 | public void should_clear_metric() { 103 | EventBuilder eventBuilder = new EventBuilder(); 104 | Proto.Event event = eventBuilder 105 | .host("riemann.localhost") 106 | .metric(123) 107 | .metric() 108 | .build(); 109 | 110 | assertEquals(false, event.hasMetricD()); 111 | assertEquals(false, event.hasMetricF()); 112 | assertEquals(false, event.hasMetricSint64()); 113 | assertEquals(0D, event.getMetricD(), 0D); 114 | assertEquals(0F, event.getMetricF(), 0F); 115 | assertEquals(0L, event.getMetricSint64(), 0L); 116 | } 117 | 118 | @Test 119 | public void should_clear_time() { 120 | EventBuilder eventBuilder = new EventBuilder(); 121 | Proto.Event event = eventBuilder 122 | .host("riemann.localhost") 123 | .time(System.currentTimeMillis()) 124 | .time() 125 | .build(); 126 | 127 | assertEquals(false, event.hasTime()); 128 | assertEquals(0L, event.getTime()); 129 | assertEquals(0L, event.getTimeMicros()); 130 | } 131 | 132 | @Test 133 | public void should_clear_ttl() { 134 | EventBuilder eventBuilder = new EventBuilder(); 135 | Proto.Event event = eventBuilder 136 | .host("riemann.localhost") 137 | .ttl(20) 138 | .ttl() 139 | .build(); 140 | 141 | assertEquals(false, event.hasTtl()); 142 | assertEquals(0F, event.getTtl(), 0F); 143 | } 144 | 145 | private List getAttributesSortedByKey(Proto.Event event) { 146 | List attributes = new ArrayList(event.getAttributesList()); 147 | Collections.sort(attributes, new Comparator() { 148 | @Override 149 | public int compare(Proto.Attribute o1, Proto.Attribute o2) { 150 | return o1.getKey().compareTo(o2.getKey()); 151 | } 152 | }); 153 | return attributes; 154 | } 155 | 156 | private void assertEqualsAttribute(String expectedKey, String expectedValue, Proto.Attribute actual) { 157 | assertEquals(expectedKey, actual.getKey()); 158 | assertEquals(expectedValue, actual.getValue()); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /riemann-java-client/src/test/java/riemann/java/client/tests/OkServer.java: -------------------------------------------------------------------------------- 1 | package riemann.java.client.tests; 2 | 3 | import io.riemann.riemann.Proto.Msg; 4 | 5 | public class OkServer extends Server { 6 | public Msg handle(final Msg m) { 7 | return Msg.newBuilder().build(); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /riemann-java-client/src/test/java/riemann/java/client/tests/PromiseTest.java: -------------------------------------------------------------------------------- 1 | package riemann.java.client.tests; 2 | 3 | import io.riemann.riemann.client.Promise; 4 | import java.lang.Runnable; 5 | import java.lang.Thread; 6 | import java.util.concurrent.TimeUnit; 7 | import org.junit.Before; 8 | import org.junit.Test; 9 | import static org.junit.Assert.assertEquals; 10 | import java.io.IOException; 11 | 12 | public class PromiseTest { 13 | 14 | private Promise p; 15 | 16 | @Before 17 | public void setUp() { 18 | p = new Promise(); 19 | } 20 | 21 | @Test 22 | public void singleTest() throws IOException { 23 | p.deliver("foo"); 24 | assertEquals("foo", p.deref()); 25 | } 26 | 27 | @Test 28 | public void threadTest() throws IOException { 29 | new Thread(new Runnable() { 30 | public void run() { 31 | try { 32 | Thread.sleep(100); 33 | } catch (InterruptedException e) { 34 | System.out.println("interrupted"); 35 | } 36 | p.deliver("bar"); 37 | } 38 | }).start(); 39 | assertEquals("bar", p.deref()); 40 | } 41 | 42 | @Test 43 | public void timeoutTest() throws IOException { 44 | assertEquals(null, p.deref(1, TimeUnit.MILLISECONDS)); 45 | assertEquals("failed", p.deref(1, TimeUnit.MILLISECONDS, "failed")); 46 | 47 | new Thread(new Runnable() { 48 | public void run() { 49 | try { 50 | Thread.sleep(100); 51 | } catch (InterruptedException e) { 52 | System.out.println("interrupted"); 53 | } 54 | p.deliver("done"); 55 | } 56 | }).start(); 57 | assertEquals("not yet", p.deref(50, TimeUnit.MILLISECONDS, "not yet")); 58 | assertEquals("done", p.deref(100, TimeUnit.SECONDS)); 59 | } 60 | 61 | @Test 62 | public void runtimeExceptionTest() throws IOException { 63 | RuntimeException thrown = null; 64 | p.deliver(new RuntimeException("fail")); 65 | try { 66 | p.deref(); 67 | } catch (RuntimeException e) { 68 | thrown = e; 69 | } 70 | assertEquals("fail", thrown.getMessage()); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /riemann-java-client/src/test/java/riemann/java/client/tests/Server.java: -------------------------------------------------------------------------------- 1 | package riemann.java.client.tests; 2 | 3 | import java.util.Random; 4 | import java.util.concurrent.LinkedBlockingQueue; 5 | import java.net.*; 6 | import java.io.*; 7 | 8 | import io.riemann.riemann.client.RiemannClient; 9 | import io.riemann.riemann.Proto.Attribute; 10 | import io.riemann.riemann.Proto.Event; 11 | import io.riemann.riemann.Proto.Msg; 12 | 13 | public abstract class Server { 14 | public int port; 15 | public Thread thread; 16 | public ServerSocket serverSocket; 17 | public LinkedBlockingQueue received = new LinkedBlockingQueue(); 18 | 19 | public InetSocketAddress start() throws IOException { 20 | this.serverSocket = new ServerSocket(0); 21 | this.port = serverSocket.getLocalPort(); 22 | this.serverSocket.setReceiveBufferSize(100); 23 | this.thread = mainThread(this.serverSocket); 24 | this.thread.setPriority(10); 25 | this.thread.start(); 26 | 27 | return new InetSocketAddress(InetAddress.getLocalHost(), this.port); 28 | } 29 | 30 | public void stop() { 31 | if (this.thread != null) { 32 | this.thread.interrupt(); 33 | this.thread = null; 34 | } 35 | this.port = -1; 36 | this.serverSocket = null; 37 | } 38 | 39 | public Thread mainThread(final ServerSocket serverSocket) { 40 | return new Thread() { 41 | @Override 42 | public void run() { 43 | try { 44 | Socket sock = null; 45 | DataOutputStream out = null; 46 | DataInputStream in = null; 47 | 48 | try { 49 | // Accept connection 50 | while((sock = serverSocket.accept()) != null) { 51 | // Set up streams 52 | out = new DataOutputStream(sock.getOutputStream()); 53 | in = new DataInputStream(sock.getInputStream()); 54 | 55 | // Over each message 56 | while (true) { 57 | // Read length 58 | final int len = in.readInt(); 59 | 60 | // Read message 61 | final byte[] data = new byte[len]; 62 | in.readFully(data); 63 | final Msg request = Msg.parseFrom(data); 64 | 65 | // Log request 66 | received.put(request); 67 | 68 | // Handle message 69 | final Msg response = handle(request); 70 | 71 | // Write response 72 | out.writeInt(response.getSerializedSize()); 73 | response.writeTo(out); 74 | } 75 | } 76 | } catch (SocketException e) { 77 | // Socket closed. 78 | } catch (EOFException e) { 79 | // Socket closed. 80 | } finally { 81 | if (out != null) { out.close(); } 82 | if (in != null) { in.close(); } 83 | if (sock != null) { sock.close(); } 84 | } 85 | } catch (Exception e) { 86 | e.printStackTrace(); 87 | } 88 | } 89 | }; 90 | } 91 | 92 | public abstract Msg handle(final Msg m); 93 | } 94 | -------------------------------------------------------------------------------- /riemann-java-client/src/test/java/riemann/java/client/tests/SimpleUdpClientTest.java: -------------------------------------------------------------------------------- 1 | package riemann.java.client.tests; 2 | 3 | import io.riemann.riemann.Proto; 4 | import io.riemann.riemann.client.RiemannClient; 5 | import io.riemann.riemann.client.ServerError; 6 | import io.riemann.riemann.client.SimpleUdpTransport; 7 | import org.junit.Test; 8 | 9 | import java.io.IOException; 10 | import java.net.DatagramPacket; 11 | import java.net.DatagramSocket; 12 | import java.net.InetSocketAddress; 13 | 14 | import static org.junit.Assert.assertEquals; 15 | import static org.junit.Assert.assertFalse; 16 | import static org.junit.Assert.assertTrue; 17 | import static org.junit.Assert.fail; 18 | 19 | public class SimpleUdpClientTest { 20 | @Test 21 | public void sendEventsTest() throws IOException, InterruptedException, ServerError { 22 | final DatagramSocket serverSocket = new DatagramSocket(); 23 | try { 24 | final RiemannClient client = new RiemannClient(new SimpleUdpTransport(serverSocket.getLocalPort())); 25 | try { 26 | client.connect(); 27 | sendTestMessages(serverSocket, client); 28 | } finally { 29 | client.close(); 30 | } 31 | } finally { 32 | serverSocket.close(); 33 | } 34 | } 35 | 36 | @Test 37 | public void sendEventsOverReconnectionTest() throws IOException, InterruptedException, ServerError { 38 | DatagramSocket serverSocket = new DatagramSocket(); 39 | final int port = serverSocket.getLocalPort(); 40 | try { 41 | final RiemannClient client = new RiemannClient(new SimpleUdpTransport(serverSocket.getLocalPort())); 42 | try { 43 | client.connect(); 44 | assertTrue(client.isConnected()); 45 | sendTestMessages(serverSocket, client); 46 | 47 | // Close listening socket 48 | serverSocket.close(); 49 | 50 | // Expect send to drop messages silently 51 | final Proto.Event e = Util.createEvent(); 52 | client.sendEvent(e); 53 | 54 | // Reopen listening socket 55 | serverSocket = new DatagramSocket(new InetSocketAddress(port)); 56 | 57 | // Expect sent messages to be received again 58 | sendTestMessages(serverSocket, client); 59 | 60 | } finally { 61 | client.close(); 62 | assertFalse(client.isConnected()); 63 | } 64 | } finally { 65 | serverSocket.close(); 66 | } 67 | } 68 | 69 | private void sendTestMessages(final DatagramSocket serverSocket, final RiemannClient client) throws IOException { 70 | for (int i = 0; i < 10; i++) { 71 | final byte[] buffer = new byte[16384]; 72 | final DatagramPacket packet = new DatagramPacket(buffer, buffer.length); 73 | final Proto.Event e = Util.createEvent(); 74 | client.sendEvent(e); 75 | serverSocket.receive(packet); 76 | final Proto.Msg msg = Proto.Msg.getDefaultInstance().newBuilderForType().mergeFrom(packet.getData(), 0, packet.getLength()).build(); 77 | assertEquals(e, Util.soleEvent(msg)); 78 | } 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /riemann-java-client/src/test/java/riemann/java/client/tests/TcpClientTest.java: -------------------------------------------------------------------------------- 1 | 2 | package riemann.java.client.tests; 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertTrue; 5 | import static org.junit.Assert.fail; 6 | 7 | import io.riemann.riemann.Proto.Event; 8 | import io.riemann.riemann.Proto.Msg; 9 | import io.riemann.riemann.client.IPromise; 10 | import io.riemann.riemann.client.IRiemannClient; 11 | import io.riemann.riemann.client.OverloadedException; 12 | import io.riemann.riemann.client.RiemannClient; 13 | import io.riemann.riemann.client.ServerError; 14 | import io.riemann.riemann.client.TcpTransport; 15 | import java.io.IOException; 16 | import java.util.ArrayList; 17 | import java.util.List; 18 | import java.util.concurrent.Semaphore; 19 | import java.util.concurrent.TimeUnit; 20 | import org.junit.Test; 21 | 22 | public class TcpClientTest { 23 | @Test 24 | public void sendEventsTest() throws IOException, InterruptedException, ServerError { 25 | final Server server = new OkServer(); 26 | RiemannClient client = null; 27 | try { 28 | client = RiemannClient.tcp(server.start()); 29 | client.connect(); 30 | for (int i = 0; i < 10; i++) { 31 | final Event e = Util.createEvent(); 32 | final Msg rsp = client.sendEvent(e).deref(); 33 | assertEquals(true, !rsp.hasOk() || rsp.getOk()); 34 | assertEquals(e, Util.soleEvent(server.received.poll())); 35 | } 36 | } finally { 37 | if (client != null) { 38 | client.close(); 39 | } 40 | server.stop(); 41 | } 42 | } 43 | 44 | @Test 45 | public void queryTest() throws IOException, InterruptedException, ServerError { 46 | final Server server = new EchoServer(); 47 | RiemannClient client = null; 48 | try { 49 | client = RiemannClient.tcp(server.start()); 50 | client.connect(); 51 | for (int i = 0; i < 10; i++) { 52 | final List events = client.query("hi").deref(); 53 | assertEquals(0, events.size()); 54 | final Msg m = server.received.poll(); 55 | assertEquals("hi", m.getQuery().getString()); 56 | } 57 | } finally { 58 | if (client != null) { 59 | client.close(); 60 | } 61 | server.stop(); 62 | } 63 | } 64 | 65 | 66 | @Test 67 | public void limiterLeakTest() throws IOException { 68 | final long delay = 0; // Server time to process a message 69 | final Server server = new EchoServer(delay); 70 | IRiemannClient client = null; 71 | 72 | try { 73 | client = RiemannClient.tcp(server.start()); 74 | TcpTransport transport = (TcpTransport) client.transport(); 75 | int limit = 2; 76 | transport.writeLimiter = new Semaphore(limit); 77 | // Simulate the case where the server gets down after the client 78 | // connected and thus no channel is available 79 | transport.state = TcpTransport.State.CONNECTED; 80 | 81 | for (int i = 0; i < limit; i++) { 82 | try { 83 | client.event().service("foo").metric(1).send().deref(); 84 | fail("Should throw IOException when there is no channel available"); 85 | } catch (IOException e) { 86 | assertEquals("no channels available", e.getMessage()); 87 | } 88 | } 89 | 90 | transport.state = TcpTransport.State.DISCONNECTED; 91 | client.connect(); 92 | 93 | // Should not throw OverloadedException. 94 | client.event().service("foo").metric(1).send().deref(); 95 | } finally { 96 | if (client != null) { 97 | client.close(); 98 | } 99 | server.stop(); 100 | } 101 | } 102 | 103 | @Test 104 | public void overloadTest() throws IOException, InterruptedException { 105 | boolean debug = true; 106 | // Milliseconds 107 | final long delay = 5; // Server time to process a message 108 | final long fast = 1; // Async latencies 109 | final double slow = ((double) delay) * 0.95; // Backpressure latencies 110 | 111 | final Server server = new EchoServer(delay); 112 | IRiemannClient client = null; 113 | 114 | try { 115 | client = RiemannClient.tcp(server.start()); 116 | ((TcpTransport) client.transport()).setWriteBufferLimit(5); 117 | client.connect(); 118 | 119 | final int n = 5000; 120 | final List> responses = new ArrayList>(n); 121 | long latency; 122 | long t0; 123 | 124 | // Warm up the server 125 | for (int j = 0; j < 100; j++) { 126 | responses.add(client.event().service("slow").metric(j).send()); 127 | } 128 | for (IPromise response : responses) { 129 | try { 130 | response.deref(); 131 | } catch (Exception e) { 132 | // NO-OP 133 | } 134 | } 135 | responses.clear(); 136 | 137 | // Queue up a bunch of writes 138 | for (int i = 0; i < n; i++) { 139 | // Measure the time it takes to call .send() 140 | t0 = System.nanoTime(); 141 | responses.add(client.event().service("slow").metric(i).send()); 142 | latency = System.nanoTime() - t0; 143 | assertTrue(latency <= 100000000); 144 | 145 | if (i % 2 == 0) { 146 | Thread.sleep(1); 147 | } 148 | if (debug && i % 100 == 0) { 149 | System.out.println(i + " sent out of " + n); 150 | } 151 | } 152 | 153 | // Deref all and spew out success/failure pairs 154 | // 0: success 155 | // 1: timeout 156 | // 2: overload 157 | // 3: other 158 | final ArrayList results = new ArrayList(); 159 | int state = -1; 160 | int count = 0; 161 | long deadline = System.currentTimeMillis() + 2000; 162 | for (IPromise response : responses) { 163 | try { 164 | if (null == response.deref(deadline - System.currentTimeMillis(), 165 | TimeUnit.MILLISECONDS)) { 166 | // Timeout 167 | if (state == -1) { 168 | state = 1; 169 | } else if (state != 1) { 170 | results.add(new int[]{state, count}); 171 | state = 1; 172 | } 173 | } else { 174 | // OK 175 | if (state == -1) { 176 | state = 0; 177 | } else if (state != 0) { 178 | results.add(new int[]{state, count}); 179 | state = 0; 180 | count = 0; 181 | } 182 | } 183 | } catch (OverloadedException e) { 184 | // Not OK 185 | if (state == -1) { 186 | state = 2; 187 | } else if (state != 2) { 188 | results.add(new int[]{state, count}); 189 | state = 2; 190 | count = 0; 191 | } 192 | } catch (Exception e) { 193 | // Huh? 194 | if (state == -1) { 195 | state = 3; 196 | } else if (state != 3) { 197 | results.add(new int[]{state, count}); 198 | state = 3; 199 | count = 0; 200 | } 201 | } 202 | count++; 203 | } 204 | 205 | // Print outcomes 206 | if (debug) { 207 | for (int[] res : results) { 208 | if (res[0] == 0) { 209 | System.out.println("ok\t\t" + res[1]); 210 | } else if (res[0] == 1) { 211 | System.out.println("timeout\t" + res[1]); 212 | } else if (res[0] == 2) { 213 | System.out.println("overload\t" + res[1]); 214 | } else { 215 | System.out.println("other\t\t" + res[1]); 216 | } 217 | } 218 | } 219 | 220 | // OKs should come first 221 | assertTrue(0 == results.get(0)[0]); 222 | // Should be a lot of OKs 223 | int initialOkCount = results.get(0)[1]; 224 | System.out.println("Count of initial oks: " + initialOkCount); 225 | assertTrue(10 < results.get(0)[1]); 226 | 227 | // Tally up totals 228 | int[] counts = new int[4]; 229 | for (int[] res : results) { 230 | counts[res[0]] += res[1]; 231 | } 232 | 233 | // Should see both overloads and timeouts 234 | assertTrue("No Overloads", 0 < counts[1]); 235 | assertTrue("No timeouts", 0 < counts[2]); 236 | 237 | // No others 238 | assertTrue(counts[3] == 0); 239 | } finally { 240 | if (client != null) { 241 | client.close(); 242 | } 243 | server.stop(); 244 | } 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /riemann-java-client/src/test/java/riemann/java/client/tests/Util.java: -------------------------------------------------------------------------------- 1 | package riemann.java.client.tests; 2 | 3 | import io.riemann.riemann.Proto.Attribute; 4 | import io.riemann.riemann.Proto.Event; 5 | import io.riemann.riemann.Proto.Msg; 6 | import java.util.*; 7 | import org.junit.Test; 8 | import static org.junit.Assert.assertEquals; 9 | import static org.junit.Assert.fail; 10 | 11 | public class Util { 12 | public static Event createEvent() { 13 | final Random random = new Random(); 14 | return Event.newBuilder() 15 | .setDescription("desc") 16 | .setHost("127.0.0.1") 17 | .setService("service") 18 | .setTime(random.nextInt(1000)) 19 | .setTtl(random.nextInt(1000)) 20 | .setMetricF(random.nextInt(1000)) 21 | .addAllTags(Arrays.asList("tag1", "tag2")) 22 | .addAttributes(Attribute.newBuilder().setKey("key1").setValue("value1")) 23 | .build(); 24 | } 25 | 26 | // Returns a single event from a message, asserting that it has only one 27 | // event. 28 | public static Event soleEvent(final Msg m) { 29 | assertEquals(1, m.getEventsCount()); 30 | return m.getEvents(0); 31 | } 32 | 33 | public static void compareEvents(final Event e1, final Event e2) { 34 | assertEquals(e1.getHost(), e2.getHost()); 35 | assertEquals(e1.getService(), e2.getService()); 36 | assertEquals(e1.getState(), e2.getState()); 37 | assertEquals(e1.getDescription(), e2.getDescription()); 38 | assertEquals(e1.getMetricF(), e2.getMetricF(), 0); 39 | assertEquals(e1.getTime(), e2.getTime(), 0); 40 | assertEquals(e1.getTtl(), e2.getTtl(), 0); 41 | assertEquals(e1.getTagsList(), e2.getTagsList()); 42 | assertEquals(e1.getAttributesList(), e2.getAttributesList()); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /scripts/install-protobuf.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -ex 3 | if [ ! -f "$HOME/bin/protoc" ]; then 4 | cd $HOME 5 | wget https://github.com/google/protobuf/releases/download/v3.16.1/protoc-3.16.1-linux-x86_64.zip 6 | unzip protoc-3.16.1-linux-x86_64.zip 7 | else 8 | echo "Using cached directory." 9 | fi 10 | --------------------------------------------------------------------------------