├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── NOTICE.txt ├── README.md ├── USE_CASE.md ├── metadata.json ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── appdynamics │ │ └── extensions │ │ └── redis │ │ ├── RedisCommandHandler.java │ │ ├── RedisMonitor.java │ │ ├── RedisMonitorTask.java │ │ ├── metrics │ │ ├── CommonMetricsModifier.java │ │ ├── InfoMetrics.java │ │ └── SlowLogMetrics.java │ │ └── utils │ │ ├── Constants.java │ │ └── InfoMapExtractor.java └── resources │ └── conf │ ├── config.yml │ └── monitor.xml └── test ├── java └── com │ └── appdynamics │ └── extensions │ └── redis │ ├── metrics │ ├── InfoMetricsTest.java │ └── SlowLogMetricsTest.java │ └── utils │ └── InfoMapExtractorTest.java └── resources ├── conf └── config.yml ├── info.txt ├── log4j.xml └── slowlog.txt /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | *.iml 3 | .settings 4 | .classpath 5 | .project 6 | *.log 7 | *.ipr 8 | *.iws 9 | .idea 10 | .DS_Store 11 | out/ 12 | 13 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Redis Monitoring Extension 2 | 3 | ## 3.0.3 - Jan, 2025 4 | 1. Fix a problem with "count=" 5 | 6 | ## 3.0.2 - Sept, 2024 7 | 1. Updated ext commons with vulnerability fixes. 8 | 2. Fixed junit vulnerability. 9 | 10 | ## 3.0.1 - Jan, 2021 11 | 1. Updated to new extensions framework(2.2.4). 12 | 2. Fixed issue for global encryptionKey 13 | 3. Fixed issue for heartbeat metric 14 | 15 | ## 3.0.0 - May, 2020 16 | 1. Updated to new extensions framework(2.2.3). 17 | 2. Added the SSL connection ability from the extension. 18 | 19 | ## 2.0.2 - Feb 22, 2019 20 | 1. Fixed the documentation bug in config.yml. 21 | 22 | ## 2.0.1 - Mar 29, 2018 23 | 1. Added copyright, LICENSE.txt and NOTICE.txt. 24 | 25 | ## 2.0.0 - May 27, 2018 26 | 1. Revamped the extension to support new extensions framework(2.0.0). 27 | 2. Added new metrics like "no_of_new_slow_logs", "connectionStatus. 28 | 29 | ## 1.0.7 30 | 1. Fix for includePatterns. 31 | 32 | ## 1.0.6 33 | 1. Added code fixes. 34 | 35 | ## 1.0.5 36 | 1. Added commandstats and keyspace_hit_ratio. 37 | 38 | ## 1.0.4 39 | 1. JDK 1.6 compatible. 40 | 41 | ## 1.0.3 42 | 1. Revamped and Added more metrics. 43 | 44 | ## 1.0.2 45 | 1. Support for role metrics. 46 | 47 | ## 1.0.1 48 | 1. Code Optimization. 49 | 50 | ## 1.0.0 51 | 1. Initial version. 52 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2017 AppDynamics LLC and its affiliates 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /NOTICE.txt: -------------------------------------------------------------------------------- 1 | Notice and Disclaimer 2 | 3 | All Extensions published by AppDynamics are governed by the Apache License v2 and are excluded from the definition of covered software under any agreement between AppDynamics and the User governing AppDynamics Pro Edition, Test & Dev Edition, or any other Editions. 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Redis Monitoring Extension for AppDynamics 2 | 3 | ## Use Case 4 | Redis is an in memory key-value data store used as a database, cache and message broker. It supports data structures such as strings, hashes, lists, sets, 5 | sorted sets with range queries, bitmaps, hyperloglogs and geospatial indexes with radius queries. 6 | 7 | The Redis monitoring extension can monitor multiple Redis servers and display the statistics in AppDynamics Metric Browser. 8 | 9 | ## Prerequisites 10 | 1. Before the extension is installed, the prerequisites mentioned [here](https://community.appdynamics.com/t5/Knowledge-Base/Extensions-Prerequisites-Guide/ta-p/35213) need to be met. Please do not proceed with the extension installation if the specified prerequisites are not met. 11 | 2. Download and install [Apache Maven](https://maven.apache.org/) which is configured with `Java 8` to build the extension artifact from source. You can check the java version used in maven using command `mvn -v` or `mvn --version`. If your maven is using some other java version then please download java 8 for your platform and set JAVA_HOME parameter before starting maven. 12 | 3. This extension creates a java client to the Redis server that needs to be monitored. So the Redis server that has to be monitored, should be available 13 | for access from the machine that has the extension installed. 14 | 15 | ## Installation 16 | 1. Clone the "redis-monitoring-extension" repo using `git clone ` command. 17 | 2. Run 'mvn clean install' from "redis-monitoring-extension". 18 | 3. Unzip the `RedisMonitor-.zip` from `target` directory into the "/monitors" directory. 19 | 4. Edit the file config.yml located at /monitors/RedisMonitor referring to configurations section below. 20 | 5. Restart the Machine Agent. 21 | 22 | ## Recommendations 23 | It is recommended that a single Redis monitoring extension be used to monitor multiple Redis servers belonging to a single cluster. 24 | 25 | ## Configuring the extension using config.yml 26 | Configure the Redis monitoring extension by editing the config.yml file in `/monitors/RedisMonitor/` 27 | 28 | 1. Configure the "tier" under which the metrics need to be reported. This can be done by changing the value of `` in 29 | metricPrefix: "Server|Component:``|Custom Metrics|Redis". 30 | 31 | For example, 32 | ``` 33 | metricPrefix: "Server|Component:Extensions tier|Custom Metrics|Redis" 34 | ``` 35 | More details around metric prefix can be found [here](https://community.appdynamics.com/t5/Knowledge-Base/How-do-I-troubleshoot-missing-custom-metrics-or-extensions/ta-p/28695) 36 | 37 | 2. Configure the Redis instances by specifying the name(required), host(required), port(required) of the Redis instance, password (only if authentication enabled), 38 | encryptedPassword(only if password encryption required). 39 | 40 | For example, 41 | ``` 42 | #Add your list of Redis servers here. 43 | servers: 44 | - name: "Server1" 45 | host: "localhost" 46 | port: "6379" 47 | password: "" 48 | encryptedPassword: "" 49 | - name: "Server2" 50 | host: "localhost" 51 | port: "6380" 52 | password: "" 53 | encryptedPassword: "" 54 | ``` 55 | 56 | 3. Configure the encyptionKey for encryptionPasswords(only if password encryption required). 57 | 58 | For example, 59 | ``` 60 | #Encryption key for Encrypted password. 61 | encryptionKey: "axcdde43535hdhdgfiniyy576" 62 | ``` 63 | 64 | 4. Configure the numberOfThreads(only if the number of Redis servers need to be monitored is greater than 7). 65 | 66 | For example, 67 | 68 | If number Redis servers that need to be monitored is 10, then number of threads required is 10 * 3 = 30 69 | ``` 70 | numberOfThreads: 30 71 | ``` 72 | 73 | 5. Configure the metrics section. 74 | 75 | For configuring the metrics, the following properties can be used: 76 | 77 | | Property | Default value | Possible values | Description | 78 | | :---------------- | :-------------- | :------------------------------ | :------------------------------------------------------------------------------------------------------------- | 79 | | alias | metric name | Any string | The substitute name to be used in the metric browser instead of metric name. | 80 | | aggregationType | "AVERAGE" | "AVERAGE", "SUM", "OBSERVATION" | [Aggregation qualifier](https://docs.appdynamics.com/display/latest/Build+a+Monitoring+Extension+Using+Java) | 81 | | timeRollUpType | "AVERAGE" | "AVERAGE", "SUM", "CURRENT" | [Time roll-up qualifier](https://docs.appdynamics.com/display/latest/Build+a+Monitoring+Extension+Using+Java) | 82 | | clusterRollUpType | "INDIVIDUAL" | "INDIVIDUAL", "COLLECTIVE" | [Cluster roll-up qualifier](https://docs.appdynamics.com/display/latest/Build+a+Monitoring+Extension+Using+Java)| 83 | | multiplier | 1 | Any number | Value with which the metric needs to be multiplied. | 84 | | convert | null | Any key value map | Set of key value pairs that indicates the value to which the metrics need to be transformed. eg: UP:0, DOWN:1 | 85 | | delta | false | true, false | If enabled, gives the delta values of metrics instead of actual values. | 86 | 87 | For example, 88 | ``` 89 | - total_connections_received: #Total number of connections accepted by the server 90 | alias: "connectionsReceived" 91 | multiplier: 1 92 | aggregationType: "SUM" 93 | timeRollUpType: "CURRENT" 94 | clusterRollUpType: "INDIVIDUAL" 95 | delta: true 96 | - role: #Role of Redis server(master or slave) 97 | convert: 98 | master: 1 99 | slave: 0 100 | ``` 101 | **All these metric properties are optional, and the default value shown in the table is applied to the metric(if a property has not been specified) by default.** 102 | 103 | 104 | ## Metrics 105 | This extension uses [INFO](http://redis.io/commands/info) command to fetch metrics from Redis server. Some of the metrics are listed below: 106 | ``` 107 | * Clients: connected_clients, blocked_clients 108 | * Memory: used_memory, used_memory_rss, used_memory_peak, used_memory_lua, mem_fragmentation_ratio 109 | * Stats: total_connections_received, total_commands_processed, keyspace_hits, keyspace_misses, keyspace_hit_ratio 110 | * Persistence: rdb_changes_since_last_save, aof_last_rewrite_time_sec 111 | * replication: role (MASTER:1, SLAVE:0), connected_slaves 112 | * CPU: used_cpu_sys, used_cpu_user, used_cpu_sys_children, used_cpu_user_children 113 | ``` 114 | This extension also uses [SLOWLOG](https://redis.io/commands/slowlog) to fetch metrics from Redis server. 115 | ``` 116 | * no_of_new_slow_logs -> This metric represents the number of new logs that were recorded as slowlogs(log queries that exceeded a specified 117 | execution time) since the extension has recorded in its previous run. 118 | To use this metric, the "slowlog-log-slower-than" config parameter has to be set for the Redis server. 119 | ``` 120 | In addition to the above metrics, there is a metric called "connectionStatus" with a value 0 when the connection to Redis server failed and 1 when the connection to the Redis server is successful. 121 | 122 | ## Credentials Encryption 123 | Please visit [this page](https://community.appdynamics.com/t5/Knowledge-Base/How-to-use-Password-Encryption-with-Extensions/ta-p/29397) to get detailed instructions on password encryption. The steps in this document will guide you through the whole process. 124 | 125 | ## Extensions Workbench 126 | 127 | Workbench is an inbuilt feature provided with each extension in order to assist you to fine tune the extension setup before you actually deploy it on the controller. Please review the following document on [How to use the Extensions WorkBench](https://community.appdynamics.com/t5/Knowledge-Base/How-to-use-the-Extensions-WorkBench/ta-p/30130) 128 | 129 | ## Troubleshooting 130 | 131 | Please follow the steps listed in this [troubleshooting-document](https://community.appdynamics.com/t5/Knowledge-Base/How-to-troubleshoot-missing-custom-metrics-or-extensions-metrics/ta-p/28695) in order to troubleshoot your issue. These are a set of common issues that customers might have faced during the installation of the extension. 132 | 133 | ## Contributing 134 | 135 | Always feel free to fork and contribute any changes directly here on [GitHub](https://github.com/Appdynamics/redis-monitoring-extension). 136 | 137 | ## Version 138 | 139 | | | | 140 | |--------------------------|-------------------------------------------------------------------------------------------------| 141 | |Current version | 3.0.3 | 142 | |Redis version tested on | 3.9, 4.0.8 | 143 | |Last Update | 10/01/2025 | 144 | |Changes list | [ChangeLog](https://github.com/Appdynamics/redis-monitoring-extension/blob/master/CHANGELOG.md) | 145 | 146 | **Note**: While extensions are maintained and supported by customers under the open-source licensing model, they interact with agents and Controllers that are subject to [AppDynamics’ maintenance and support policy](https://docs.appdynamics.com/latest/en/product-and-release-announcements/maintenance-support-for-software-versions). Some extensions have been tested with AppDynamics 4.5.13+ artifacts, but you are strongly recommended against using versions that are no longer supported. 147 | -------------------------------------------------------------------------------- /USE_CASE.md: -------------------------------------------------------------------------------- 1 | # Redis Monitoring Extension for AppDynamics 2 | 3 | Redis is an in memory key-value data store used as a database, cache and message broker. It supports data structures such as strings, hashes, lists, sets, sorted sets with range queries, bitmaps, hyperloglogs and geospatial indexes with radius queries. 4 | 5 | The Redis monitoring extension can monitor multiple Redis servers and display the statistics in AppDynamics Metric Browser. 6 | 7 | ## Related Sandbox 8 | 9 | For reference only (Extension deployment is not possible on the current sandbox) [Cisco AppDynamics sandbox](https://devnetsandbox.cisco.com/RM/Diagram/Index/9e056219-ab84-4741-9485-de3d3446caf2?diagramType=Topology) 10 | 11 | ## Links to DevNet Learning Labs 12 | 13 | [AppDynamics Fundamentals](https://developer.cisco.com/learning/modules/appdynamics-fundamentals) 14 | 15 | [Installation and Configuration workflow](https://github.com/Appdynamics/redis-monitoring-extension/blob/master/README.md) -------------------------------------------------------------------------------- /metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "id" : "redis-monitor", 3 | "type" : "monitor", 4 | "displayName" : "Redis Monitoring Extension", 5 | "description" : "An AppDynamics extension to be used with a stand alone Java machine agent to provide metrics for Redis servers.", 6 | "version" : "2.0.0", 7 | "downloadLink" : "https://github.com/Appdynamics/redis-monitoring-extension/releases/download/v2.0.0/RedisMonitor-2.0.0.zip", 8 | "imageLink" : "https:///www.appdynamics.com/media/uploaded-images/1510098508/.thumbnails/redis-logo-stack-52x0.png", 9 | "configs" : [ 10 | { 11 | "name" : "monitor.xml", 12 | "link" : "https://raw.githubusercontent.com/Appdynamics/redis-monitoring-extension/master/src/main/resources/conf/monitor.xml" 13 | }, 14 | { 15 | "name" : "config.yml", 16 | "link" : "https://raw.githubusercontent.com/Appdynamics/redis-monitoring-extension/master/src/main/resources/conf/config.yml" 17 | } 18 | ] 19 | 20 | } 21 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.appdynamics.extensions 8 | redis-monitoring-extension 9 | 3.0.3 10 | jar 11 | 12 | 13 | UTF-8 14 | yyyy-MM-dd HH:mm:ss 15 | ${project.build.directory}/RedisMonitor 16 | true 17 | 18 | 19 | 20 | 21 | com.appdynamics 22 | machine-agent 23 | 3.7.11 24 | provided 25 | 26 | 27 | com.appdynamics 28 | appd-exts-commons 29 | 2.2.13 30 | 31 | 32 | redis.clients 33 | jedis 34 | 2.9.0 35 | 36 | 37 | org.mockito 38 | mockito-all 39 | 1.9.5 40 | test 41 | 42 | 43 | junit 44 | junit 45 | 4.13.1 46 | test 47 | 48 | 49 | 50 | 51 | ${project.artifactId} 52 | 53 | 54 | src/main/resources 55 | 56 | 57 | 58 | 59 | src/test/resources 60 | 61 | 62 | 63 | 64 | org.apache.maven.plugins 65 | maven-compiler-plugin 66 | 2.3.2 67 | 68 | 1.8 69 | 1.8 70 | 71 | 72 | 73 | org.apache.maven.plugins 74 | maven-shade-plugin 75 | 2.2 76 | 77 | false 78 | 79 | 80 | *:* 81 | 82 | META-INF/*.SF 83 | META-INF/*.DSA 84 | META-INF/*.RSA 85 | 86 | 87 | 88 | 89 | 90 | 91 | package 92 | 93 | shade 94 | 95 | 96 | 97 | 98 | 99 | 100 | com.appdynamics.extensions.workbench.WorkbenchServerLauncher 101 | v${project.version} Build Date ${maven.build.timestamp} 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | org.apache.maven.plugins 111 | maven-antrun-plugin 112 | 1.6 113 | 114 | 115 | install 116 | install 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | run 141 | 142 | 143 | 144 | 145 | 146 | org.codehaus.mojo 147 | versions-maven-plugin 148 | 2.1 149 | 150 | 151 | maven-scm-plugin 152 | 1.8.1 153 | 154 | ${project.artifactId}-${project.version} 155 | 156 | 157 | 158 | org.codehaus.mojo 159 | build-helper-maven-plugin 160 | 3.0.0 161 | 162 | 163 | add-integration-test-source 164 | generate-test-sources 165 | 166 | add-test-source 167 | 168 | 169 | 170 | src/integration-test/java 171 | 172 | 173 | 174 | 175 | 176 | 177 | maven-failsafe-plugin 178 | 2.22.0 179 | 180 | ${skipITs} 181 | 182 | 183 | 184 | 185 | integration-test 186 | verify 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | github-maven-repo 196 | 197 | true 198 | 199 | 200 | true 201 | 202 | https://github.com/Appdynamics/maven-repo/raw/master/releases 203 | 204 | 205 | -------------------------------------------------------------------------------- /src/main/java/com/appdynamics/extensions/redis/RedisCommandHandler.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 AppDynamics, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.appdynamics.extensions.redis; 17 | 18 | import com.appdynamics.extensions.MetricWriteHelper; 19 | import com.appdynamics.extensions.conf.MonitorContextConfiguration; 20 | import com.appdynamics.extensions.redis.metrics.InfoMetrics; 21 | import com.appdynamics.extensions.redis.metrics.SlowLogMetrics; 22 | import org.slf4j.Logger; 23 | import org.slf4j.LoggerFactory; 24 | import redis.clients.jedis.JedisPool; 25 | 26 | import java.util.Map; 27 | import java.util.concurrent.CountDownLatch; 28 | 29 | class RedisCommandHandler { 30 | 31 | private MonitorContextConfiguration monitorContextConfiguration; 32 | private Map server; 33 | private MetricWriteHelper metricWriteHelper; 34 | private JedisPool jedisPool; 35 | private Logger logger = LoggerFactory.getLogger(RedisCommandHandler.class); 36 | private CountDownLatch countDownLatch; 37 | private long previousTimeStamp; 38 | private long currentTimeStamp; 39 | 40 | RedisCommandHandler(MonitorContextConfiguration monitorContextConfiguration, Map server, MetricWriteHelper metricWriteHelper, JedisPool jedisPool, long previousTimeStamp, long currentTimeStamp) { 41 | this.monitorContextConfiguration = monitorContextConfiguration; 42 | this.server = server; 43 | this.metricWriteHelper = metricWriteHelper; 44 | this.jedisPool = jedisPool; 45 | countDownLatch = new CountDownLatch(2); 46 | this.previousTimeStamp = previousTimeStamp; 47 | this.currentTimeStamp = currentTimeStamp; 48 | } 49 | 50 | void triggerCommandsToRedisServer() { 51 | SlowLogMetrics slowLogMetricsTask = new SlowLogMetrics(monitorContextConfiguration, server, metricWriteHelper, jedisPool, countDownLatch, previousTimeStamp, currentTimeStamp); 52 | monitorContextConfiguration.getContext().getExecutorService().execute("SlowLogMetricsTask",slowLogMetricsTask); 53 | InfoMetrics infoMetricsTask = new InfoMetrics(monitorContextConfiguration, server, metricWriteHelper, jedisPool, countDownLatch); 54 | monitorContextConfiguration.getContext().getExecutorService().execute("InfoMetricsTask", infoMetricsTask); 55 | try{ 56 | countDownLatch.await(); 57 | } 58 | catch(InterruptedException e){ 59 | logger.error("Issue in the RedisCommandHandler", e); 60 | } 61 | finally { 62 | jedisPool.destroy(); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/com/appdynamics/extensions/redis/RedisMonitor.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 AppDynamics, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.appdynamics.extensions.redis; 17 | 18 | import com.appdynamics.extensions.ABaseMonitor; 19 | import com.appdynamics.extensions.TasksExecutionServiceProvider; 20 | import com.appdynamics.extensions.util.AssertUtils; 21 | import org.slf4j.Logger; 22 | import org.slf4j.LoggerFactory; 23 | 24 | import java.util.List; 25 | import java.util.Map; 26 | 27 | import static com.appdynamics.extensions.redis.utils.Constants.DEFAULT_METRIC_PREFIX; 28 | 29 | public class RedisMonitor extends ABaseMonitor { 30 | 31 | private static final Logger logger = LoggerFactory.getLogger(RedisMonitor.class); 32 | private static long previousTimeStamp = System.currentTimeMillis(); 33 | private static long currentTimeStamp = System.currentTimeMillis(); 34 | 35 | @Override 36 | protected String getDefaultMetricPrefix() { 37 | return DEFAULT_METRIC_PREFIX; 38 | } 39 | 40 | @Override 41 | public String getMonitorName() { 42 | return "Redis Monitor"; 43 | } 44 | 45 | @Override 46 | protected void doRun(TasksExecutionServiceProvider serviceProvider) { 47 | previousTimeStamp = currentTimeStamp; 48 | currentTimeStamp = System.currentTimeMillis(); 49 | List> servers = (List>)getContextConfiguration().getConfigYml().get("servers"); 50 | AssertUtils.assertNotNull(servers, "The 'servers' section in config.yml is not initialised"); 51 | for (Map server : servers) { 52 | RedisMonitorTask task = new RedisMonitorTask(getContextConfiguration(), serviceProvider, server, previousTimeStamp, currentTimeStamp); 53 | serviceProvider.submit((String)server.get("name"),task); 54 | } 55 | } 56 | 57 | @Override 58 | protected List> getServers() { 59 | List> servers = (List>)getContextConfiguration().getConfigYml().get("servers"); 60 | AssertUtils.assertNotNull(servers, "The 'servers' section in config.yml is not initialised"); 61 | return servers; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/main/java/com/appdynamics/extensions/redis/RedisMonitorTask.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2017 AppDynamics, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.appdynamics.extensions.redis; 17 | 18 | import com.appdynamics.extensions.AMonitorTaskRunnable; 19 | import com.appdynamics.extensions.MetricWriteHelper; 20 | import com.appdynamics.extensions.TasksExecutionServiceProvider; 21 | import com.appdynamics.extensions.conf.MonitorContextConfiguration; 22 | import com.appdynamics.extensions.util.CryptoUtils; 23 | import com.appdynamics.extensions.util.SSLUtils; 24 | import com.appdynamics.extensions.util.StringUtils; 25 | import com.google.common.base.Strings; 26 | import org.slf4j.Logger; 27 | import org.slf4j.LoggerFactory; 28 | import redis.clients.jedis.JedisPool; 29 | import redis.clients.jedis.JedisPoolConfig; 30 | 31 | import javax.net.ssl.HostnameVerifier; 32 | import javax.net.ssl.SSLContext; 33 | import javax.net.ssl.SSLParameters; 34 | import javax.net.ssl.SSLSocketFactory; 35 | import java.util.Map; 36 | 37 | class RedisMonitorTask implements AMonitorTaskRunnable { 38 | 39 | private static final Logger logger = LoggerFactory.getLogger(RedisMonitorTask.class); 40 | private MonitorContextConfiguration contextConfiguration; 41 | private Map server; 42 | private MetricWriteHelper metricWriteHelper; 43 | private long previousTimeStamp; 44 | private long currentTimeStamp; 45 | private JedisPool jedisPool; 46 | 47 | RedisMonitorTask(MonitorContextConfiguration contextConfiguration, TasksExecutionServiceProvider serviceProvider, Map server, long previousTimeStamp, long currentTimeStamp) { 48 | this.contextConfiguration = contextConfiguration; 49 | this.server = server; 50 | this.metricWriteHelper = serviceProvider.getMetricWriteHelper(); 51 | this.previousTimeStamp = previousTimeStamp; 52 | this.currentTimeStamp = currentTimeStamp; 53 | } 54 | 55 | public void run() { 56 | populateAndPrintMetrics(); 57 | } 58 | 59 | private void populateAndPrintMetrics() { 60 | String host = (String) server.get("host"); 61 | String port = (String) server.get("port"); 62 | String name = (String) server.get("name"); 63 | Boolean useSSL = (Boolean) server.get("useSSL"); 64 | String encryptionKey = (String) contextConfiguration.getConfigYml().get("encryptionKey"); 65 | if(!Strings.isNullOrEmpty(host) && !Strings.isNullOrEmpty(port) && !Strings.isNullOrEmpty(name)) { 66 | int portNumber = Integer.parseInt(port); 67 | String password = CryptoUtils.getPassword(server,encryptionKey); 68 | JedisPoolConfig jedisPoolConfig = buildJedisPoolConfig(); 69 | if (password.trim().length() == 0) { 70 | password = null; 71 | } 72 | SSLSocketFactory sslSocketFactory = null; 73 | SSLParameters sslParameters = null; 74 | HostnameVerifier hostnameVerifier = null; 75 | 76 | if(useSSL && contextConfiguration.getConfigYml().get("connection") != null) { 77 | try { 78 | SSLContext sslContext = SSLUtils.createSSLContext(null, contextConfiguration.getConfigYml()); 79 | sslSocketFactory = sslContext.getSocketFactory(); 80 | sslParameters = sslContext.getSupportedSSLParameters(); 81 | } catch (Exception e) { 82 | logger.debug("Failed to create custom SSLContext, falling back on the Java default SSLContext", e); 83 | } 84 | hostnameVerifier = SSLUtils.createHostNameVerifier((Map) contextConfiguration.getConfigYml().get("connection")); 85 | logger.debug("Created custom hostnameverifier"); 86 | } 87 | if (Strings.isNullOrEmpty(password)) { 88 | jedisPool = new JedisPool(jedisPoolConfig, host, portNumber, 2000, useSSL, sslSocketFactory, sslParameters, hostnameVerifier); 89 | } else { 90 | jedisPool = new JedisPool(jedisPoolConfig, host, portNumber, 2000, password, useSSL, sslSocketFactory, sslParameters, hostnameVerifier); 91 | } 92 | getMetricsFromInfo(jedisPool); 93 | } 94 | else{ 95 | logger.debug("The host, port and name fields of the server : {} need to be specified", server); 96 | } 97 | } 98 | 99 | private JedisPoolConfig buildJedisPoolConfig(){ 100 | JedisPoolConfig jedisPoolConfig = new JedisPoolConfig(); 101 | jedisPoolConfig.setMaxTotal(3); 102 | return jedisPoolConfig; 103 | } 104 | 105 | private void getMetricsFromInfo(JedisPool jedisPool) { 106 | RedisCommandHandler redisCommandHandler = new RedisCommandHandler(contextConfiguration, server, metricWriteHelper, jedisPool, previousTimeStamp, currentTimeStamp); 107 | redisCommandHandler.triggerCommandsToRedisServer(); 108 | } 109 | 110 | @Override 111 | public void onTaskComplete() { 112 | } 113 | } 114 | 115 | 116 | 117 | 118 | 119 | 120 | -------------------------------------------------------------------------------- /src/main/java/com/appdynamics/extensions/redis/metrics/CommonMetricsModifier.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013. AppDynamics LLC and its affiliates. 3 | * * All Rights Reserved. 4 | * * This is unpublished proprietary source code of AppDynamics LLC and its affiliates. 5 | * * The copyright notice above does not evidence any actual or intended publication of such source code. 6 | */ 7 | 8 | package com.appdynamics.extensions.redis.metrics; 9 | 10 | import com.appdynamics.extensions.metrics.Metric; 11 | import com.google.common.base.Strings; 12 | import com.google.common.collect.Lists; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | 16 | import java.util.List; 17 | import java.util.Map; 18 | 19 | import static com.appdynamics.extensions.redis.utils.Constants.METRIC_SEPARATOR; 20 | 21 | class CommonMetricsModifier { 22 | 23 | private List> individualSectionFields; 24 | private Map individualSectionInfoMap; 25 | private String metricPrefix; 26 | private String sectionName; 27 | private static final Logger logger = LoggerFactory.getLogger(CommonMetricsModifier.class); 28 | 29 | CommonMetricsModifier(List> individualSectionFields, Map individualSectionInfoMap, String metricPrefix, String sectionName){ 30 | this.individualSectionFields = individualSectionFields; 31 | this.individualSectionInfoMap = individualSectionInfoMap; 32 | this.metricPrefix = metricPrefix; 33 | this.sectionName = sectionName; 34 | } 35 | 36 | List metricBuilder(){ 37 | List individualSectionMetricsList = Lists.newArrayList(); 38 | for(Map individualMetricMap : individualSectionFields){ //Iterates through the list of metrics for the specific section("sectionName") and adds each metric in the individualSectionMetrics Map. 39 | String actualMetricName = individualMetricMap.entrySet().iterator().next().getKey(); 40 | Map metricModifierMap = (Map) individualMetricMap.get(actualMetricName); 41 | String actualIndividualMetricValue = individualSectionInfoMap.get(actualMetricName); 42 | String metricPathWithoutMetricName = metricPrefix + METRIC_SEPARATOR + sectionName; 43 | if(!Strings.isNullOrEmpty(actualIndividualMetricValue)){ 44 | Metric metric; 45 | String metricPath = metricPathWithoutMetricName + METRIC_SEPARATOR + actualMetricName; 46 | if(metricModifierMap.size() == 0) { 47 | metric = new Metric(actualMetricName, actualIndividualMetricValue, metricPath); 48 | } 49 | else{ 50 | metric = new Metric(actualMetricName, actualIndividualMetricValue, metricPath, metricModifierMap); 51 | } 52 | logger.debug("Value for {} under {} is : {}", actualMetricName, metricPathWithoutMetricName, actualIndividualMetricValue); 53 | individualSectionMetricsList.add(metric); 54 | } 55 | else{ 56 | logger.debug("Value for {} under {} not available", actualMetricName, metricPathWithoutMetricName); 57 | } 58 | } 59 | return individualSectionMetricsList; 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/main/java/com/appdynamics/extensions/redis/metrics/InfoMetrics.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013. AppDynamics LLC and its affiliates. 3 | * * All Rights Reserved. 4 | * * This is unpublished proprietary source code of AppDynamics LLC and its affiliates. 5 | * * The copyright notice above does not evidence any actual or intended publication of such source code. 6 | */ 7 | 8 | package com.appdynamics.extensions.redis.metrics; 9 | 10 | import com.appdynamics.extensions.MetricWriteHelper; 11 | import com.appdynamics.extensions.conf.MonitorContextConfiguration; 12 | import com.appdynamics.extensions.metrics.Metric; 13 | import com.appdynamics.extensions.redis.utils.InfoMapExtractor; 14 | import com.appdynamics.extensions.util.AssertUtils; 15 | import com.google.common.collect.Lists; 16 | import org.slf4j.Logger; 17 | import org.slf4j.LoggerFactory; 18 | import redis.clients.jedis.Jedis; 19 | import redis.clients.jedis.JedisPool; 20 | 21 | import java.util.List; 22 | import java.util.Map; 23 | import java.util.concurrent.CountDownLatch; 24 | 25 | import static com.appdynamics.extensions.redis.utils.Constants.METRIC_SEPARATOR; 26 | 27 | public class InfoMetrics implements Runnable { 28 | 29 | private JedisPool jedisPool; 30 | private String info; 31 | private Map metricsMap; 32 | private Map infoMap; 33 | private List finalMetricList; 34 | private MonitorContextConfiguration configuration; 35 | private Map server; 36 | private MetricWriteHelper metricWriteHelper; 37 | private static final Logger logger = LoggerFactory.getLogger(InfoMetrics.class); 38 | private CountDownLatch countDownLatch; 39 | 40 | public InfoMetrics(MonitorContextConfiguration configuration, Map server, MetricWriteHelper metricWriteHelper, JedisPool jedisPool, CountDownLatch countDownLatch) { 41 | this.configuration = configuration; 42 | this.server = server; 43 | this.metricWriteHelper = metricWriteHelper; 44 | this.jedisPool = jedisPool; 45 | this.countDownLatch = countDownLatch; 46 | finalMetricList = Lists.newArrayList(); 47 | } 48 | 49 | public void run() { 50 | try { 51 | metricsMap = (Map)configuration.getConfigYml().get("metrics"); 52 | AssertUtils.assertNotNull(metricsMap, "There is no 'metrics' section in config.yml"); 53 | infoMap = (Map)metricsMap.get("Info"); 54 | AssertUtils.assertNotNull(infoMap, "There is no 'Info' metrics section under 'metrics' in config.yml"); 55 | info = extractInfo(); 56 | if(info != null) { 57 | finalMetricList.addAll(extractMetricsList()); 58 | logger.debug("Printing Info metrics for server {}", server.get("name")); 59 | } 60 | metricWriteHelper.transformAndPrintMetrics(finalMetricList); 61 | } 62 | catch(Exception e){ 63 | logger.error("Error while collecting and printing info metrics", e); 64 | } 65 | finally { 66 | countDownLatch.countDown(); 67 | } 68 | } 69 | 70 | private String extractInfo(){ 71 | int heartbeat = 0; 72 | String infoFromRedis = null; 73 | Jedis jedis = null; 74 | try { 75 | jedis = jedisPool.getResource(); 76 | infoFromRedis = jedis.info(); 77 | heartbeat = 1; 78 | } 79 | catch(Exception e){ 80 | logger.error("Error while collecting info metrics", e); 81 | } 82 | finally { 83 | if(jedis != null) { 84 | jedis.close(); 85 | } 86 | finalMetricList.add(new Metric("HeartBeat", String.valueOf(heartbeat), configuration.getMetricPrefix() + "|" + server.get("name") + "|" + "HeartBeat", "AVERAGE", "AVERAGE", "INDIVIDUAL")); 87 | } 88 | return infoFromRedis; 89 | } 90 | 91 | private List extractMetricsList(){ 92 | List finalMetricList = Lists.newArrayList(); 93 | InfoMapExtractor infoMapExtractor = new InfoMapExtractor(); 94 | String metricPrefix = configuration.getMetricPrefix() + METRIC_SEPARATOR + server.get("name"); 95 | for(Map.Entry entry : infoMap.entrySet()) { 96 | String sectionName = entry.getKey().toString(); 97 | List> metricsInSectionConfig = (List>) entry.getValue(); 98 | Map sectionInfoMap = infoMapExtractor.extractInfoAsHashMap(info, sectionName); 99 | CommonMetricsModifier commonMetricsModifier = new CommonMetricsModifier(metricsInSectionConfig, sectionInfoMap, metricPrefix, sectionName); 100 | finalMetricList.addAll(commonMetricsModifier.metricBuilder()); 101 | } 102 | return finalMetricList; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /src/main/java/com/appdynamics/extensions/redis/metrics/SlowLogMetrics.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013. AppDynamics LLC and its affiliates. 3 | * * All Rights Reserved. 4 | * * This is unpublished proprietary source code of AppDynamics LLC and its affiliates. 5 | * * The copyright notice above does not evidence any actual or intended publication of such source code. 6 | */ 7 | 8 | package com.appdynamics.extensions.redis.metrics; 9 | 10 | import com.appdynamics.extensions.MetricWriteHelper; 11 | import com.appdynamics.extensions.conf.MonitorContextConfiguration; 12 | import com.appdynamics.extensions.metrics.Metric; 13 | import com.appdynamics.extensions.util.AssertUtils; 14 | import com.google.common.collect.Lists; 15 | import org.slf4j.Logger; 16 | import org.slf4j.LoggerFactory; 17 | import redis.clients.jedis.Jedis; 18 | import redis.clients.jedis.JedisPool; 19 | import redis.clients.util.Slowlog; 20 | 21 | import java.util.List; 22 | import java.util.ListIterator; 23 | import java.util.Map; 24 | import java.util.concurrent.CountDownLatch; 25 | 26 | import static com.appdynamics.extensions.redis.utils.Constants.METRIC_SEPARATOR; 27 | 28 | public class SlowLogMetrics implements Runnable { 29 | 30 | private JedisPool jedisPool; 31 | private Map metricsMap; 32 | private MonitorContextConfiguration configuration; 33 | private Map server; 34 | private MetricWriteHelper metricWriteHelper; 35 | private Map metricPropertiesMap; 36 | private static final Logger logger = LoggerFactory.getLogger(SlowLogMetrics.class); 37 | private CountDownLatch countDownLatch; 38 | private long previousTimeStamp; 39 | private long currentTimeStamp; 40 | private List> slowLogMetricsList; 41 | private List finalMetricList; 42 | 43 | public SlowLogMetrics(MonitorContextConfiguration configuration, Map server, MetricWriteHelper metricWriteHelper, JedisPool jedisPool, CountDownLatch countDownLatch, long previousTimeStamp, long currentTimeStamp){ 44 | this.configuration = configuration; 45 | this.server = server; 46 | this.metricWriteHelper = metricWriteHelper; 47 | this.jedisPool = jedisPool; 48 | this.countDownLatch = countDownLatch; 49 | this.previousTimeStamp = previousTimeStamp / 1000L; 50 | this.currentTimeStamp = currentTimeStamp / 1000L; 51 | } 52 | 53 | public void run() { 54 | try { 55 | metricsMap = (Map)configuration.getConfigYml().get("metrics"); 56 | AssertUtils.assertNotNull(metricsMap, "There is no 'metrics' section in config.yml"); 57 | slowLogMetricsList = (List>)metricsMap.get("Slowlog"); 58 | AssertUtils.assertNotNull(slowLogMetricsList, "There is no 'Slowlog' metrics section under 'metrics' in config.yml"); 59 | extractSlowLogPropertiesMap(slowLogMetricsList); 60 | finalMetricList = extractSlowLogMetricsList(); 61 | logger.debug("Printing SlowLog metrics for server {}", server.get("name")); 62 | metricWriteHelper.transformAndPrintMetrics(finalMetricList); 63 | } 64 | catch(Exception e){ 65 | logger.error("Error while collecting and printing slowlog metrics", e); 66 | } 67 | finally { 68 | countDownLatch.countDown(); 69 | } 70 | } 71 | 72 | private void extractSlowLogPropertiesMap(List> slowLogMetricsList) { 73 | ListIterator> list = slowLogMetricsList.listIterator(); 74 | while(list.hasNext()){ 75 | Map metricEntries = list.next(); 76 | for(Map.Entry metricEntry : metricEntries.entrySet()) { 77 | if(metricEntry.getKey().equalsIgnoreCase("no_of_new_slow_logs")){ 78 | metricPropertiesMap = (Map) metricEntry.getValue(); 79 | return; 80 | } 81 | } 82 | } 83 | } 84 | 85 | private List extractSlowLogMetricsList() { 86 | List finalMetricList = Lists.newArrayList(); 87 | int slowLogCount; 88 | List slowlogs; 89 | try(Jedis jedis = jedisPool.getResource()) { 90 | slowlogs = jedis.slowlogGet(jedis.slowlogLen()); 91 | } 92 | slowLogCount = countNumberOfNewSlowLogs(slowlogs); 93 | String metricName = "no_of_new_slow_logs"; 94 | String metricValue = String.valueOf(slowLogCount); 95 | String metricPath = configuration.getMetricPrefix() + METRIC_SEPARATOR + server.get("name") + METRIC_SEPARATOR + "SlowLog" + METRIC_SEPARATOR + "no_of_new_slow_logs"; 96 | Metric metric; 97 | if(metricPropertiesMap != null) { 98 | metric = new Metric(metricName, metricValue, metricPath, metricPropertiesMap); 99 | } 100 | else{ 101 | metric = new Metric(metricName, metricValue, metricPath); 102 | } 103 | finalMetricList.add(metric); 104 | return finalMetricList; 105 | } 106 | 107 | private int countNumberOfNewSlowLogs(List slowlogs) { 108 | int count = 0; 109 | if(previousTimeStamp != currentTimeStamp) { 110 | for (Slowlog individualLog : slowlogs) { 111 | Long tempTimeStamp = individualLog.getTimeStamp(); 112 | if (tempTimeStamp > previousTimeStamp && tempTimeStamp <= currentTimeStamp) { 113 | count++; 114 | } 115 | } 116 | } 117 | logger.debug("The number of new slow logs between {} and {} are : {}", previousTimeStamp, currentTimeStamp, count); 118 | return count; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /src/main/java/com/appdynamics/extensions/redis/utils/Constants.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013. AppDynamics LLC and its affiliates. 3 | * * All Rights Reserved. 4 | * * This is unpublished proprietary source code of AppDynamics LLC and its affiliates. 5 | * * The copyright notice above does not evidence any actual or intended publication of such source code. 6 | */ 7 | 8 | package com.appdynamics.extensions.redis.utils; 9 | 10 | public class Constants { 11 | public static final String DEFAULT_METRIC_PREFIX = "Custom Metrics|Redis|"; 12 | public static final String METRIC_SEPARATOR = "|"; 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/appdynamics/extensions/redis/utils/InfoMapExtractor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013. AppDynamics LLC and its affiliates. 3 | * * All Rights Reserved. 4 | * * This is unpublished proprietary source code of AppDynamics LLC and its affiliates. 5 | * * The copyright notice above does not evidence any actual or intended publication of such source code. 6 | */ 7 | 8 | package com.appdynamics.extensions.redis.utils; 9 | 10 | import com.google.common.base.Splitter; 11 | import com.google.common.collect.Maps; 12 | 13 | import java.util.Map; 14 | 15 | public class InfoMapExtractor { 16 | 17 | public Map extractInfoAsHashMap(String info, String sectionName) { 18 | Map infoMap = Maps.newHashMap(); 19 | Splitter sectionSplitter = Splitter.on('#') 20 | .omitEmptyStrings() 21 | .trimResults(); 22 | for (String section : sectionSplitter.split(info)) { 23 | if (!section.toLowerCase().startsWith(sectionName.toLowerCase())) { 24 | continue; 25 | } 26 | return sectionMapGenerator(section, sectionName); 27 | } 28 | return infoMap; 29 | } 30 | 31 | private Map sectionMapGenerator(String sectionData, String sectionName) { 32 | Map infoMap = Maps.newHashMap(); 33 | Splitter lineSplitter = Splitter.on(System.getProperty("line.separator")) 34 | .omitEmptyStrings() 35 | .trimResults(); 36 | for (String line : lineSplitter.split(sectionData)) { 37 | if (line.toLowerCase().startsWith(sectionName)) { 38 | continue; 39 | } 40 | String[] infoLine = line.split(":"); 41 | if (infoLine.length == 2 && positiveNumber(infoLine[1].trim())) { 42 | infoMap.put(infoLine[0].trim(), removeInvalidCharacters(infoLine[1].trim())); 43 | } 44 | 45 | } 46 | return infoMap; 47 | } 48 | 49 | private boolean positiveNumber(String trim) { 50 | return !trim.contains("-"); 51 | } 52 | 53 | private String removeInvalidCharacters(String metric) { 54 | return metric.replaceAll("%", "") 55 | .replaceAll("count=", ""); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/resources/conf/config.yml: -------------------------------------------------------------------------------- 1 | #This will populate the metrics in all the tiers, under this path(not recommended) 2 | #metricPrefix: "Custom Metrics|Redis" 3 | #The following prefix will populate the metrics under one tier 4 | metricPrefix: "Server|Component:|Custom Metrics|Redis" 5 | 6 | #Add your list of Redis servers here. 7 | servers: 8 | - name: "Server1" 9 | host: "localhost" 10 | port: "6379" 11 | password: "" 12 | #encryptedPassword: "" 13 | useSSL: false 14 | 15 | #Encryption key for Encrypted password. 16 | encryptionKey: "" 17 | 18 | # Each server instance needs 3 threads, one for the server instance itself, one for info call to the Redis server and one for slowlog call to the server. 19 | # So, please change the value accordingly(Based on the number of server instances you are monitoring). 20 | numberOfThreads: 21 21 | 22 | # List of metrics 23 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 24 | #Glossary of terms(These terms are used as properties for each metric): 25 | # alias 26 | # aggregationType 27 | # timeRollUpType 28 | # clusterRollUpType } 29 | # multiplier -->not for derived metrics 30 | # convert --> not for derived metrics 31 | # delta --> not for derived metrics 32 | #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 33 | metrics: 34 | Slowlog: 35 | - no_of_new_slow_logs: 36 | alias: "no_of_new_slow_logs" 37 | Info: 38 | Clients: #Information about the client connections 39 | - connected_clients: #Number of client connections (excluding connections from slaves) 40 | alias: "connected_clients" 41 | - blocked_clients: #Number of clients pending on a blocking call(BLPOP, BRPOP, BRPOPLPUSH) 42 | alias: "blocked_clients" 43 | 44 | Memory: #Information about the memory consumption 45 | - used_memory: #Total number of bytes allocated by Redis using its allocator (either standard libc, jemalloc, or an alternative allocator such as tcmalloc 46 | alias: "used_memory" 47 | - used_memory_rss: #Number of bytes that Redis allocated as seen by the operating system (a.k.a resident set size). This is the number reported by tools such as top(1) and ps(1) 48 | alias: "used_memory_rss" 49 | - used_memory_peak: #Peak memory consumed by Redis (in bytes) 50 | alias: "used_memory_peak" 51 | - used_memory_lua: #Number of bytes used by the Lua engine 52 | alias: "used_memory_lua" 53 | - mem_fragmentation_ratio: #Ratio between used_memory_rss and used_memory 54 | alias: "mem_fragmentation_ratio" 55 | 56 | 57 | Persistence: #Information related to RDB and AOF 58 | - rdb_changes_since_last_save: #Number of changes since the last dump 59 | alias: "rdb_changes_since_last_save" 60 | - rdb_last_bgsave_time_sec: #Duration of the last RDB save operation in seconds 61 | alias: "rdb_last_bgsave_time_sec" 62 | - rdb_current_bgsave_time_sec: #Duration of the on-going RDB save operation if any 63 | alias: "rdb_current_bgsave_time_sec" 64 | - aof_last_rewrite_time_sec: #Duration of the last AOF rewrite operation in seconds 65 | alias: "aof_last_rewrite_time_sec" 66 | - aof_current_rewrite_time_sec: #Duration of the on-going AOF rewrite operation if any 67 | alias: "aof_current_rewrite_time_sec" 68 | 69 | Stats: #General statistics 70 | - total_connections_received: #Total number of connections accepted by the server 71 | alias: "total_connections_received" 72 | - total_commands_processed: #Total number of commands processed by the server 73 | alias: "total_commands_processed" 74 | - instantaneous_ops_per_sec: #Number of commands processed per second 75 | alias: "instantaneous_ops_per_sec" 76 | - rejected_connections: #Number of connections rejected because of maxclients limit 77 | alias: "rejected_connections" 78 | - expired_keys: #Total number of key expiration events 79 | alias: "expired_keys" 80 | - evicted_keys: #Number of evicted keys due to maxmemory limit 81 | alias: "evicted_keys" 82 | - keyspace_hits: #Number of successful lookup of keys in the main dictionary 83 | alias: "keyspace_hits" 84 | - keyspace_misses: #Number of failed lookup of keys in the main dictionary 85 | alias: "keyspace_misses" 86 | - pubsub_channels: #Global number of pub/sub channels with client subscriptions 87 | alias: "pubsub_channels" 88 | - pubsub_patterns: #Global number of pub/sub pattern with client subscriptions 89 | alias: "pubsub_patterns" 90 | - latest_fork_usec: #Duration of the latest fork operation in microseconds 91 | alias: "latest_fork_usec" 92 | 93 | Replication: #Information related to Master/Slave replication 94 | - role: 95 | alias: "role" 96 | convert: 97 | master: "1" 98 | slave: "0" 99 | - connected_slaves: #Number of connected slaves 100 | alias: "connected_slaves" 101 | 102 | CPU: #Information related to CPU consumption 103 | - used_cpu_sys: #System CPU consumed by the Redis server 104 | alias: "used_cpu_sys" 105 | - used_cpu_user: #User CPU consumed by the Redis server 106 | alias: "used_cpu_user" 107 | - used_cpu_sys_children: #System CPU consumed by the background processes 108 | alias: "used_cpu_sys_children" 109 | - used_cpu_user_children: #User CPU consumed by the background processes 110 | alias: "used_cpu_user_children" 111 | -------------------------------------------------------------------------------- /src/main/resources/conf/monitor.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | RedisMonitor 10 | managed 11 | true 12 | Monitors a Redis key-value-store server. 13 | 14 | 15 | 16 | RedisMonitor Run Task 17 | RedisMonitor Run Task 18 | RedisMonitor Run Task 19 | java 20 | periodic 21 | 60 22 | 60 23 | 24 | 25 | 26 | 27 | 28 | redis-monitoring-extension.jar 29 | com.appdynamics.extensions.redis.RedisMonitor 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /src/test/java/com/appdynamics/extensions/redis/metrics/InfoMetricsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013. AppDynamics LLC and its affiliates. 3 | * * All Rights Reserved. 4 | * * This is unpublished proprietary source code of AppDynamics LLC and its affiliates. 5 | * * The copyright notice above does not evidence any actual or intended publication of such source code. 6 | */ 7 | 8 | package com.appdynamics.extensions.redis.metrics; 9 | 10 | import com.appdynamics.extensions.MetricWriteHelper; 11 | import com.appdynamics.extensions.conf.MonitorContextConfiguration; 12 | import com.appdynamics.extensions.metrics.Metric; 13 | import com.appdynamics.extensions.yml.YmlReader; 14 | import com.google.common.collect.Lists; 15 | import com.google.common.collect.Maps; 16 | import org.apache.commons.io.FileUtils; 17 | import org.junit.Test; 18 | import org.mockito.ArgumentCaptor; 19 | import redis.clients.jedis.Jedis; 20 | import redis.clients.jedis.JedisPool; 21 | 22 | import java.io.File; 23 | import java.io.IOException; 24 | import java.util.List; 25 | import java.util.Map; 26 | import java.util.concurrent.CountDownLatch; 27 | 28 | import static org.mockito.Mockito.*; 29 | 30 | 31 | public class InfoMetricsTest { 32 | 33 | 34 | @Test 35 | public void infoMetricsTest() throws IOException{ 36 | ArgumentCaptor pathCaptor = ArgumentCaptor.forClass(List.class); 37 | MonitorContextConfiguration monitorContextConfiguration = mock(MonitorContextConfiguration.class); 38 | Map rootElem = YmlReader.readFromFileAsMap(new File("src/test/resources/conf/config.yml")); 39 | doReturn(rootElem).when(monitorContextConfiguration).getConfigYml(); 40 | when(monitorContextConfiguration.getMetricPrefix()).thenReturn("Server|Component:AppLevels|Custom Metrics|Redis"); 41 | MetricWriteHelper metricWriteHelper = mock(MetricWriteHelper.class); 42 | JedisPool jedisPool = mock(JedisPool.class); 43 | Jedis jedis = mock(Jedis.class); 44 | CountDownLatch countDownLatch = new CountDownLatch(1); 45 | when(jedisPool.getResource()).thenReturn(jedis); 46 | String info = FileUtils.readFileToString(new File("src/test/resources/info.txt")); 47 | when(jedis.info()).thenReturn(info); 48 | Map server = Maps.newHashMap(); 49 | server.put("host", "localhost"); 50 | server.put("port", "6379"); 51 | server.put("name", "Server1"); 52 | InfoMetrics redisMetrics = new InfoMetrics(monitorContextConfiguration, server, metricWriteHelper,jedisPool, countDownLatch); 53 | redisMetrics.run(); 54 | verify(metricWriteHelper).transformAndPrintMetrics(pathCaptor.capture()); 55 | List metricPathsList = Lists.newArrayList(); 56 | metricPathsList.add("Server|Component:AppLevels|Custom Metrics|Redis|Server1|Clients|connected_clients"); 57 | metricPathsList.add("Server|Component:AppLevels|Custom Metrics|Redis|Server1|Clients|client_longest_output_list"); 58 | metricPathsList.add("Server|Component:AppLevels|Custom Metrics|Redis|Server1|Clients|client_biggest_input_buf"); 59 | metricPathsList.add("Server|Component:AppLevels|Custom Metrics|Redis|Server1|Memory|used_memory"); 60 | metricPathsList.add("Server|Component:AppLevels|Custom Metrics|Redis|Server1|Memory|used_memory_rss"); 61 | metricPathsList.add("Server|Component:AppLevels|Custom Metrics|Redis|Server1|Memory|used_memory_peak"); 62 | metricPathsList.add("Server|Component:AppLevels|Custom Metrics|Redis|Server1|Memory|used_memory_lua"); 63 | metricPathsList.add("Server|Component:AppLevels|Custom Metrics|Redis|Server1|Memory|mem_fragmentation_ratio"); 64 | metricPathsList.add("Server|Component:AppLevels|Custom Metrics|Redis|Server1|Persistence|rdb_changes_since_last_save"); 65 | metricPathsList.add("Server|Component:AppLevels|Custom Metrics|Redis|Server1|Persistence|rdb_last_bgsave_time_sec"); 66 | metricPathsList.add("Server|Component:AppLevels|Custom Metrics|Redis|Server1|Persistence|rdb_current_bgsave_time_sec"); 67 | metricPathsList.add("Server|Component:AppLevels|Custom Metrics|Redis|Server1|Persistence|aof_last_rewrite_time_sec"); 68 | metricPathsList.add("Server|Component:AppLevels|Custom Metrics|Redis|Server1|Persistence|aof_current_rewrite_time_sec"); 69 | metricPathsList.add("Server|Component:AppLevels|Custom Metrics|Redis|Server1|Stats|total_connections_received"); 70 | metricPathsList.add("Server|Component:AppLevels|Custom Metrics|Redis|Server1|Stats|total_commands_processed"); 71 | metricPathsList.add("Server|Component:AppLevels|Custom Metrics|Redis|Server1|Stats|instantaneous_ops_per_sec"); 72 | metricPathsList.add("Server|Component:AppLevels|Custom Metrics|Redis|Server1|Stats|rejected_connections"); 73 | metricPathsList.add("Server|Component:AppLevels|Custom Metrics|Redis|Server1|Stats|expired_keys"); 74 | metricPathsList.add("Server|Component:AppLevels|Custom Metrics|Redis|Server1|Stats|evicted_keys"); 75 | metricPathsList.add("Server|Component:AppLevels|Custom Metrics|Redis|Server1|Stats|keyspace_hits"); 76 | metricPathsList.add("Server|Component:AppLevels|Custom Metrics|Redis|Server1|Stats|keyspace_misses"); 77 | metricPathsList.add("Server|Component:AppLevels|Custom Metrics|Redis|Server1|Stats|pubsub_channels"); 78 | metricPathsList.add("Server|Component:AppLevels|Custom Metrics|Redis|Server1|Stats|pubsub_patterns"); 79 | metricPathsList.add("Server|Component:AppLevels|Custom Metrics|Redis|Server1|Stats|latest_fork_usec"); 80 | metricPathsList.add("Server|Component:AppLevels|Custom Metrics|Redis|Server1|Replication|connected_slaves"); 81 | metricPathsList.add("Server|Component:AppLevels|Custom Metrics|Redis|Server1|CPU|used_cpu_sys"); 82 | metricPathsList.add("Server|Component:AppLevels|Custom Metrics|Redis|Server1|CPU|used_cpu_user"); 83 | metricPathsList.add("Server|Component:AppLevels|Custom Metrics|Redis|Server1|CPU|used_cpu_sys_children"); 84 | metricPathsList.add("Server|Component:AppLevels|Custom Metrics|Redis|Server1|CPU|used_cpu_user_children"); 85 | metricPathsList.add("Server|Component:AppLevels|Custom Metrics|Redis|Server1|HeartBeat"); 86 | for (Metric metric : (List)pathCaptor.getValue()){ 87 | org.junit.Assert.assertTrue(metricPathsList.contains(metric.getMetricPath())); 88 | } 89 | } 90 | 91 | 92 | @Test 93 | public void whenConnectionFailsShouldReturnFailedHeartBeatTest() throws IOException{ 94 | ArgumentCaptor pathCaptor = ArgumentCaptor.forClass(List.class); 95 | MonitorContextConfiguration monitorContextConfiguration = mock(MonitorContextConfiguration.class); 96 | Map rootElem = YmlReader.readFromFileAsMap(new File("src/test/resources/conf/config.yml")); 97 | doReturn(rootElem).when(monitorContextConfiguration).getConfigYml(); 98 | when(monitorContextConfiguration.getMetricPrefix()).thenReturn("Server|Component:AppLevels|Custom Metrics|Redis"); 99 | MetricWriteHelper metricWriteHelper = mock(MetricWriteHelper.class); 100 | JedisPool jedisPool = mock(JedisPool.class); 101 | Jedis jedis = mock(Jedis.class); 102 | CountDownLatch countDownLatch = new CountDownLatch(1); 103 | when(jedisPool.getResource()).thenReturn(jedis); 104 | when(jedis.info()).thenThrow(new RuntimeException()); 105 | Map server = Maps.newHashMap(); 106 | server.put("host", "localhost"); 107 | server.put("port", "6379"); 108 | server.put("name", "Server1"); 109 | InfoMetrics redisMetrics = new InfoMetrics(monitorContextConfiguration, server, metricWriteHelper,jedisPool, countDownLatch); 110 | redisMetrics.run(); 111 | verify(metricWriteHelper).transformAndPrintMetrics(pathCaptor.capture()); 112 | List metricPathsList = Lists.newArrayList(); 113 | metricPathsList.add("Server|Component:AppLevels|Custom Metrics|Redis|Server1|HeartBeat"); 114 | for (Metric metric : (List)pathCaptor.getValue()){ 115 | org.junit.Assert.assertTrue(metricPathsList.contains(metric.getMetricPath())); 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/test/java/com/appdynamics/extensions/redis/metrics/SlowLogMetricsTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013. AppDynamics LLC and its affiliates. 3 | * * All Rights Reserved. 4 | * * This is unpublished proprietary source code of AppDynamics LLC and its affiliates. 5 | * * The copyright notice above does not evidence any actual or intended publication of such source code. 6 | */ 7 | 8 | package com.appdynamics.extensions.redis.metrics; 9 | 10 | import com.appdynamics.extensions.AMonitorJob; 11 | import com.appdynamics.extensions.MetricWriteHelper; 12 | import com.appdynamics.extensions.conf.MonitorContextConfiguration; 13 | import com.appdynamics.extensions.metrics.Metric; 14 | import com.appdynamics.extensions.yml.YmlReader; 15 | import com.google.common.collect.Lists; 16 | import com.google.common.collect.Maps; 17 | import org.junit.Assert; 18 | import org.junit.Test; 19 | import org.mockito.ArgumentCaptor; 20 | import redis.clients.jedis.Jedis; 21 | import redis.clients.jedis.JedisPool; 22 | import redis.clients.jedis.JedisPoolConfig; 23 | import redis.clients.util.Slowlog; 24 | 25 | import java.io.File; 26 | import java.io.IOException; 27 | import java.util.ArrayList; 28 | import java.util.List; 29 | import java.util.Map; 30 | import java.util.concurrent.CountDownLatch; 31 | 32 | import static org.mockito.Mockito.*; 33 | 34 | public class SlowLogMetricsTest { 35 | 36 | 37 | @Test 38 | public void sampleTest() { 39 | try { 40 | JedisPoolConfig jedisPoolConfig = new JedisPoolConfig(); 41 | jedisPoolConfig.setMaxTotal(3); 42 | JedisPool jedisPool = new JedisPool(jedisPoolConfig, "localhost", 6379, 2000, null, false, null, null, null); 43 | Jedis jedis = jedisPool.getResource(); 44 | String info = jedis.info(); 45 | System.out.println(info); 46 | } catch (Exception e) { 47 | e.printStackTrace(); 48 | } 49 | } 50 | 51 | @Test 52 | public void slowLogMetricsTest() { 53 | 54 | ArgumentCaptor pathCaptor = ArgumentCaptor.forClass(List.class); 55 | AMonitorJob aMonitorJob = mock(AMonitorJob.class); 56 | MonitorContextConfiguration monitorContextConfiguration = mock(MonitorContextConfiguration.class); 57 | monitorContextConfiguration.setConfigYml("src/test/resources/conf/config.yml"); 58 | when(monitorContextConfiguration.getMetricPrefix()).thenReturn("Server|Component:AppLevels|Custom Metrics|Redis"); 59 | Map rootElem = YmlReader.readFromFileAsMap(new File("src/test/resources/conf/config.yml")); 60 | doReturn(rootElem).when(monitorContextConfiguration).getConfigYml(); 61 | MetricWriteHelper metricWriteHelper = mock(MetricWriteHelper.class); 62 | JedisPool jedisPool = mock(JedisPool.class); 63 | Jedis jedis = mock(Jedis.class); 64 | CountDownLatch countDownLatch = new CountDownLatch(1); 65 | when(jedisPool.getResource()).thenReturn(jedis); 66 | List objectList = Lists.newArrayList(); 67 | List list1 = new ArrayList(); 68 | list1.add(15L); 69 | list1.add(1505158204L); 70 | list1.add(18L); 71 | List list11 = new ArrayList(); 72 | list11.add("slowlog".getBytes()); 73 | list11.add("get".getBytes()); 74 | list1.add(list11); 75 | List list2 = new ArrayList(); 76 | list2.add(14L); 77 | list2.add(1505158203L); 78 | list2.add(18L); 79 | List list21 = new ArrayList(); 80 | list21.add("slowlog".getBytes()); 81 | list21.add("get".getBytes()); 82 | list2.add(list21); 83 | objectList.add(list1); 84 | objectList.add(list2); 85 | when(jedis.slowlogGet(jedis.slowlogLen())).thenReturn(Slowlog.from(objectList)); 86 | Map server = Maps.newHashMap(); 87 | server.put("host", "localhost"); 88 | server.put("port", "6379"); 89 | server.put("name", "Server1"); 90 | SlowLogMetrics slowLogMetrics = new SlowLogMetrics(monitorContextConfiguration, server, metricWriteHelper, jedisPool, countDownLatch, 1505158200000L, 1505158220000L); 91 | slowLogMetrics.run(); 92 | verify(metricWriteHelper).transformAndPrintMetrics(pathCaptor.capture()); 93 | for (Metric metric : (List)pathCaptor.getValue()){ 94 | Assert.assertTrue(metric.getMetricPath().equals("Server|Component:AppLevels|Custom Metrics|Redis|Server1|SlowLog|no_of_new_slow_logs")); 95 | Assert.assertTrue(metric.getMetricValue().equals("2")); 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/test/java/com/appdynamics/extensions/redis/utils/InfoMapExtractorTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2013. AppDynamics LLC and its affiliates. 3 | * * All Rights Reserved. 4 | * * This is unpublished proprietary source code of AppDynamics LLC and its affiliates. 5 | * * The copyright notice above does not evidence any actual or intended publication of such source code. 6 | */ 7 | 8 | package com.appdynamics.extensions.redis.utils; 9 | 10 | import org.apache.commons.io.FileUtils; 11 | import org.junit.Assert; 12 | import org.junit.Before; 13 | import org.junit.Test; 14 | 15 | import java.io.File; 16 | import java.io.IOException; 17 | import java.util.Map; 18 | 19 | 20 | 21 | public class InfoMapExtractorTest { 22 | 23 | InfoMapExtractor infoMapExtractor = new InfoMapExtractor(); 24 | String info; 25 | @Before 26 | public void init() throws IOException{ 27 | info = FileUtils.readFileToString(new File("src/test/resources/info.txt")); 28 | } 29 | 30 | @Test 31 | public void sectionDataParseTest() throws IOException{ 32 | Map sectionInfoMap = infoMapExtractor.extractInfoAsHashMap(info, "Clients"); 33 | Assert.assertTrue(sectionInfoMap.get("connected_clients").equals("1")); 34 | Assert.assertTrue(sectionInfoMap.get("client_longest_output_list").equals("0")); 35 | Map sectionInfoMap2 = infoMapExtractor.extractInfoAsHashMap(info, "Memory"); 36 | Assert.assertTrue(sectionInfoMap2.get("used_memory").equals("1031856")); 37 | Assert.assertTrue(sectionInfoMap2.get("used_memory_human").equals("1007.67K")); 38 | } 39 | 40 | @Test 41 | public void invalidSectionDataParseTest() throws IOException{ 42 | Map sectionInfoMap3 = infoMapExtractor.extractInfoAsHashMap(info, "No"); 43 | Assert.assertTrue(sectionInfoMap3.size() == 0); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/test/resources/conf/config.yml: -------------------------------------------------------------------------------- 1 | #This will populate the metrics in all the tiers, under this path(not recommended) 2 | #metricPrefix: "Custom metrics|Redis" 3 | 4 | #The following prefix will populate the metrics under one tier 5 | metricPrefix: "Server|Component:AppLevels|Custom Metrics|Redis" 6 | 7 | #Add your list of Redis servers here. 8 | servers: 9 | - name: "Server1" 10 | host: "localhost" 11 | port: "6379" 12 | password: "" 13 | useSSL: false 14 | 15 | 16 | 17 | # Each server instance needs 3 threads, one for the server instance itself, one for info.txt call to the Redis server and one for slowlog.txt call to the server. 18 | # So, please change the value accordingly(Based on the number of server instances you are monitoring). 19 | numberOfThreads: 20 20 | 21 | 22 | 23 | metrics: 24 | Slowlog: 25 | - no_of_new_slow_logs: 26 | alias: "no_of_new_slow_logs" 27 | 28 | Info: 29 | Clients: #Information about the client connections 30 | - connected_clients: #Number of client connections (excluding connections from slaves) 31 | alias: "connected_clients" 32 | - client_longest_output_list: #Longest output list among current client connections 33 | alias: "client_longest_output_list" 34 | - client_biggest_input_buf: #Biggest input buffer among current client connections 35 | alias: "client_biggest_input_buf" 36 | - blocked_clients: #Number of clients pending on a blocking call(BLPOP, BRPOP, BRPOPLPUSH) 37 | alias: "blocked_clients" 38 | 39 | Memory: #Information about the memory consumption 40 | - used_memory: #Total number of bytes allocated by Redis using its allocator (either standard libc, jemalloc, or an alternative allocator such as tcmalloc 41 | alias: "used_memory" 42 | - used_memory_rss: #Number of bytes that Redis allocated as seen by the operating system (a.k.a resident set size). This is the number reported by tools such as top(1) and ps(1) 43 | alias: "used_memory_rss" 44 | - used_memory_peak: #Peak memory consumed by Redis (in bytes) 45 | alias: "used_memory_peak" 46 | - used_memory_lua: #Number of bytes used by the Lua engine 47 | alias: "used_memory_lua" 48 | - mem_fragmentation_ratio: #Ratio between used_memory_rss and used_memory 49 | alias: "mem_fragmentation_ratio" 50 | 51 | Persistence: #Information related to RDB and AOF 52 | - rdb_changes_since_last_save: #Number of changes since the last dump 53 | alias: "rdb_changes_since_last_save" 54 | - rdb_last_bgsave_time_sec: #Duration of the last RDB save operation in seconds 55 | alias: "rdb_last_bgsave_time_sec" 56 | - rdb_current_bgsave_time_sec: #Duration of the on-going RDB save operation if any 57 | alias: "rdb_current_bgsave_time_sec" 58 | - aof_last_rewrite_time_sec: #Duration of the last AOF rewrite operation in seconds 59 | alias: "aof_last_rewrite_time_sec" 60 | - aof_current_rewrite_time_sec: #Duration of the on-going AOF rewrite operation if any 61 | alias: "aof_current_rewrite_time_sec" 62 | 63 | Stats: #General statistics 64 | - total_connections_received: #Total number of connections accepted by the server 65 | alias: "total_connections_received" 66 | - total_commands_processed: #Total number of commands processed by the server 67 | alias: "total_commands_processed" 68 | - instantaneous_ops_per_sec: #Number of commands processed per second 69 | alias: "instantaneous_ops_per_sec" 70 | - rejected_connections: #Number of connections rejected because of maxclients limit 71 | alias: "rejected_connections" 72 | - expired_keys: #Total number of key expiration events 73 | alias: "expired_keys" 74 | - evicted_keys: #Number of evicted keys due to maxmemory limit 75 | alias: "evicted_keys" 76 | - keyspace_hits: #Number of successful lookup of keys in the main dictionary 77 | alias: "keyspace_hits" 78 | - keyspace_misses: #Number of failed lookup of keys in the main dictionary 79 | alias: "keyspace_misses" 80 | - pubsub_channels: #Global number of pub/sub channels with client subscriptions 81 | alias: "pubsub_channels" 82 | - pubsub_patterns: #Global number of pub/sub pattern with client subscriptions 83 | alias: "pubsub_patterns" 84 | - latest_fork_usec: #Duration of the latest fork operation in microseconds 85 | alias: "latest_fork_usec" 86 | - keyspace_hit_ratio: 87 | alias: "keyspace_hit_ratio" 88 | 89 | Replication: #Information related to Master/Slave replication 90 | - connected_slaves: #Number of connected slaves 91 | alias: "connected_slaves" 92 | 93 | CPU: #Information related to CPU consumption 94 | - used_cpu_sys: #System CPU consumed by the Redis server 95 | alias: "used_cpu_sys" 96 | - used_cpu_user: #User CPU consumed by the Redis server 97 | alias: "used_cpu_user" 98 | - used_cpu_sys_children: #System CPU consumed by the background processes 99 | alias: "used_cpu_sys_children" 100 | - used_cpu_user_children: #User CPU consumed by the background processes 101 | alias: "used_cpu_user_children" 102 | 103 | 104 | 105 | 106 | 107 | -------------------------------------------------------------------------------- /src/test/resources/info.txt: -------------------------------------------------------------------------------- 1 | # Server 2 | redis_version:3.2.9 3 | redis_git_sha1:00000000 4 | redis_git_dirty:0 5 | redis_build_id:90399b885d95cf76 6 | redis_mode:standalone 7 | os:Darwin 16.6.0 x86_64 8 | arch_bits:64 9 | multiplexing_api:kqueue 10 | gcc_version:4.2.1 11 | process_id:71759 12 | run_id:9dd65f7e1620c48810cb740708c98a52d7202230 13 | tcp_port:6379 14 | uptime_in_seconds:69567 15 | uptime_in_days:0 16 | hz:10 17 | lru_clock:8707764 18 | executable:/usr/local/opt/redis-3.2.9/src/redis-server 19 | config_file: 20 | 21 | # Clients 22 | connected_clients:1 23 | client_longest_output_list:0 24 | client_biggest_input_buf:0 25 | blocked_clients: 26 | 27 | # Memory 28 | used_memory:1031856 29 | used_memory_human:1007.67K 30 | used_memory_rss:1527808 31 | used_memory_rss_human:1.46M 32 | used_memory_peak:1452880 33 | used_memory_peak_human:1.39M 34 | total_system_memory:17179869184 35 | total_system_memory_human:16.00G 36 | used_memory_lua:37888 37 | used_memory_lua_human:37.00K 38 | maxmemory:0 39 | maxmemory_human:0B 40 | maxmemory_policy:noeviction 41 | mem_fragmentation_ratio:1.48 42 | mem_allocator:libc 43 | 44 | # Persistence 45 | loading:0 46 | rdb_changes_since_last_save:0 47 | rdb_bgsave_in_progress:0 48 | rdb_last_save_time:1501810421 49 | rdb_last_bgsave_status:ok 50 | rdb_last_bgsave_time_sec:-1 51 | rdb_current_bgsave_time_sec:-1 52 | aof_enabled:0 53 | aof_rewrite_in_progress:0 54 | aof_rewrite_scheduled:0 55 | aof_last_rewrite_time_sec:-1 56 | aof_current_rewrite_time_sec:-1 57 | aof_last_bgrewrite_status:ok 58 | aof_last_write_status:ok 59 | 60 | # Stats 61 | total_connections_received:161 62 | total_commands_processed:446 63 | instantaneous_ops_per_sec:0 64 | total_net_input_bytes:12587 65 | total_net_output_bytes:6617622 66 | instantaneous_input_kbps:0.00 67 | instantaneous_output_kbps:0.00 68 | rejected_connections:0 69 | sync_full:0 70 | sync_partial_ok:0 71 | sync_partial_err:0 72 | expired_keys:0 73 | evicted_keys:0 74 | keyspace_hits:2 75 | keyspace_misses:2 76 | pubsub_channels:0 77 | pubsub_patterns:0 78 | latest_fork_usec:0 79 | migrate_cached_sockets:0 80 | 81 | # Replication 82 | role:master 83 | connected_slaves:0 84 | master_repl_offset:0 85 | repl_backlog_active:0 86 | repl_backlog_size:1048576 87 | repl_backlog_first_byte_offset:0 88 | repl_backlog_histlen:0 89 | 90 | # CPU 91 | used_cpu_sys:9.87 92 | used_cpu_user:4.91 93 | used_cpu_sys_children:0.00 94 | used_cpu_user_children:0.00 95 | 96 | # Cluster 97 | cluster_enabled:0 98 | 99 | # Keyspace 100 | db0:keys=41,expires=0,avg_ttl=0 -------------------------------------------------------------------------------- /src/test/resources/log4j.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/test/resources/slowlog.txt: -------------------------------------------------------------------------------- 1 | 1) 1) (integer) 15 2 | 2) (integer) 1505158204 3 | 3) (integer) 18 4 | 4) 1) "slowlog" 5 | 2) "get" 6 | 2) 1) (integer) 14 7 | 2) (integer) 1505158204 8 | 3) (integer) 45 9 | 4) 1) "slowlog" 10 | 2) "get" 11 | 3) 1) (integer) 13 12 | 2) (integer) 1505158203 13 | 3) (integer) 20 14 | 4) 1) "slowlog" 15 | 2) "get" 16 | 4) 1) (integer) 12 17 | 2) (integer) 1505158203 18 | 3) (integer) 39 19 | 4) 1) "slowlog" 20 | 2) "get" 21 | 5) 1) (integer) 11 22 | 2) (integer) 1505158203 23 | 3) (integer) 34 24 | 4) 1) "slowlog" 25 | 2) "get" 26 | 6) 1) (integer) 10 27 | 2) (integer) 1505158203 28 | 3) (integer) 34 29 | 4) 1) "slowlog" 30 | 2) "get" 31 | 7) 1) (integer) 9 32 | 2) (integer) 1505158202 33 | 3) (integer) 41 34 | 4) 1) "slowlog" 35 | 2) "get" 36 | 8) 1) (integer) 8 37 | 2) (integer) 1505158202 38 | 3) (integer) 30 39 | 4) 1) "slowlog" 40 | 2) "get" 41 | 9) 1) (integer) 7 42 | 2) (integer) 1505158202 43 | 3) (integer) 28 44 | 4) 1) "slowlog" 45 | 2) "get" 46 | 10) 1) (integer) 6 47 | 2) (integer) 1505158201 48 | 3) (integer) 27 49 | 4) 1) "slowlog" 50 | 2) "get" 51 | --------------------------------------------------------------------------------