├── .classpath ├── .gitignore ├── .project ├── CATEGORIES.TXT ├── CHANGELOG.md ├── LICENSE ├── MYSQL.TXT ├── README.md ├── build.xml ├── config ├── metric.category.json ├── newrelic.template.json └── plugin.template.json ├── dist ├── newrelic_mysql_plugin-0.8.1-beta.tar.gz ├── newrelic_mysql_plugin-0.8.2-beta.tar.gz ├── newrelic_mysql_plugin-0.8.3-beta.tar.gz ├── newrelic_mysql_plugin-1.0.0.tar.gz ├── newrelic_mysql_plugin-1.0.1.tar.gz ├── newrelic_mysql_plugin-1.0.2.tar.gz ├── newrelic_mysql_plugin-1.0.3.tar.gz ├── newrelic_mysql_plugin-1.0.4.tar.gz ├── newrelic_mysql_plugin-1.0.5.tar.gz ├── newrelic_mysql_plugin-1.0.6.tar.gz ├── newrelic_mysql_plugin-1.0.7.tar.gz ├── newrelic_mysql_plugin-1.0.8.tar.gz ├── newrelic_mysql_plugin-1.0.9.tar.gz ├── newrelic_mysql_plugin-1.1.0.tar.gz ├── newrelic_mysql_plugin-1.2.0.tar.gz └── newrelic_mysql_plugin-2.0.0.tar.gz ├── lib ├── metrics_publish-2.0.0.jar └── mysql-connector-java-5.1.24-bin.jar ├── scripts ├── etc │ ├── default │ │ └── newrelic-mysql-plugin │ ├── init.d │ │ ├── newrelic-mysql-plugin.debian │ │ └── newrelic-mysql-plugin.rh │ └── init │ │ └── newrelic-mysql-plugin.conf └── mysql_user.sql ├── src └── com │ └── newrelic │ └── plugins │ └── mysql │ ├── MetricMeta.java │ ├── MySQL.java │ ├── instance │ ├── Main.java │ ├── MySQLAgent.java │ └── MySQLAgentFactory.java │ └── util │ └── Constants.java └── test ├── com └── newrelic │ └── plugins │ └── mysql │ ├── TestMetricMeta.java │ ├── TestMySQL.java │ └── instance │ ├── TestMySQLAgent.java │ └── TestMySQLAgentFactory.java └── lib ├── hamcrest-core-1.3.jar └── junit-4.11.jar /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | newrelic_mysql_plugin.jar 2 | build/ 3 | META-INF/ 4 | bin/ 5 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | newrelic_mysql_extension 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.jdt.core.javanature 16 | 17 | 18 | -------------------------------------------------------------------------------- /CATEGORIES.TXT: -------------------------------------------------------------------------------- 1 | MySQL Metric Categories 2 | ----------------------- 3 | 4 | The New Relic plugin is an extensible architecture, allowing you to define your 5 | own new MySQL metrics that can be obtained via an SQL statement. As new MySQL 6 | versions are released, or with additional MySQL third-party storage engines 7 | additional information is available for gathering and instrumenting. 8 | 9 | Providing an SQL statement can return either a set of rows of key/value results, 10 | or return a single row of many columns with a single value, and those values are 11 | numeric (a New Relic limitation), the MySQL plugin can record this information 12 | in New Relic. 13 | 14 | The config/metric.category.json file includes the default categories of MySQL metrics 15 | and provides some additional examples for reference. 16 | 17 | The obvious commands SHOW GLOBAL STATUS, SHOW MASTER STATUS and SHOW SLAVE STATUS 18 | satisfy the previous formatting requirements. Additionally commands that select 19 | information from the INFORMATION_SCHEMA, PERFORMANCE_SCHEMA or other tables are possible, 20 | for example in MySQL 5.5 SELECT * FROM INFORMATION_SCHEMA.innodb_buffer_pool_stats and 21 | in MySQL 5.6 SELECT name, count FROM INFORMATION_SCHEMA.innodb_metrics provide additional 22 | information. 23 | 24 | The JSON attributes are: 25 | 26 | * category - The description in the metric names 27 | * SQL - The SQL statement 28 | * result - "row", "set", or "special" 29 | * value_metrics - A CSV list of metrics that are values 30 | * counter_metrics - A CSV list of metrics that are per second values 31 | 32 | Sample list of available metrics: 33 | 34 | * status - general status metrics 35 | * master - master status metrics 36 | * slave - slave status metrics including replication metrics 37 | * buffer_pool_stats - buffer pool status metrics 38 | * innodb_status - innodb status metrics 39 | * innodb_metrics - innodb information metrics 40 | * innodb_mutex - innodb mutex metrics 41 | 42 | Note: The innodb_mutex metric category can lead to increased memory usage for the plugin 43 | if the monitored database is under a high level of contention (i.e. large numbers of active mutexes). 44 | 45 | Currently, the New Relic Platform does not allow for customized dashboards per user. 46 | In the future this feature will enable custom categories to be fully utilized. 47 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## New Relic MySQL Java Plugin ## 2 | 3 | ### v2.0.0 - April 4, 2014 ### 4 | 5 | **Features** 6 | 7 | * Support for the New Relic Platform Installer CLI tool `npi` 8 | 9 | **Changes** 10 | 11 | * Configuring the MySQL DB instances that will be monitored has moved from the `mysql.instance.json` file to the new standard `plugin.json` file. More information is available in the README.md 12 | * Setting the New Relic license key is now done in the new standard `newrelic.json` file 13 | * Logging configuration has been simplified and is done in the `newrelic.json` file 14 | 15 | ### v1.2.1 - March 21, 2014 ### 16 | 17 | **Bug Fixes** 18 | 19 | * Fixed a metric value issue where very large or very small metric values would cause errors when reporting data to New Relic 20 | 21 | ### v1.2.0 - December 31, 2013 ### 22 | 23 | **Bug Fixes** 24 | 25 | * Fixed integer narrowing for reported metrics 26 | 27 | ### v1.1.0 - December 2, 2013 ### 28 | 29 | **Features** 30 | 31 | * Added numerous improvements to memory and CPU performance 32 | * Updated README 33 | 34 | ### v1.0.9 - October 29, 2013 ### 35 | 36 | **Features** 37 | 38 | * Reported metrics for different components are consolidated into one REST request 39 | 40 | ### v1.0.8 - October 22, 2013 ### 41 | 42 | **Features** 43 | 44 | * Reduced log clutter for non-InnoDB users 45 | 46 | ### v1.0.7 - September 4, 2013 ### 47 | 48 | **Features** 49 | 50 | * Added support for metric aggregation when the agent cannot connect to New Relic's Metric API 51 | 52 | ### v1.0.6 - July 24, 2013 ### 53 | 54 | **Features** 55 | 56 | * Added support for automatic handling of component durations between successful metric publishes 57 | 58 | ### v1.0.5 - July 17, 2013 ### 59 | 60 | **Bug Fixes** 61 | 62 | * Fixing mutex metric issue 63 | 64 | ### v1.0.4 - July 15, 2013 ### 65 | 66 | **Bug Fixes** 67 | 68 | * Improved null handling for JDBC connections 69 | 70 | **Features** 71 | 72 | * Improved error logging 73 | 74 | ### v1.0.3 - July 2, 2013 ### 75 | 76 | **Bug Fixes** 77 | 78 | * Fixed issue where duplicate metric data was being sent 79 | 80 | ### v1.0.2 - June 24, 2013 ### 81 | 82 | **Bug Fixes** 83 | 84 | * Fixed metric parsing for values that may include ip addresses 85 | 86 | **Features** 87 | 88 | * Improved logging 89 | 90 | ### v1.0.1 - June 20, 2013 ### 91 | 92 | **Features** 93 | 94 | * Improved MySQL connection testing 95 | 96 | ### v1.0.0 - June 18, 2013 ### 97 | 98 | **Features** 99 | 100 | * Initial release version of the New Relic MySQL Java Plugin 101 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2013 New Relic, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /MYSQL.TXT: -------------------------------------------------------------------------------- 1 | Required MySQL User Account 2 | =========================== 3 | 4 | The following SQL statements that are found in the mysql_user.sql file will create a default 5 | New Relic MySQL user necessary to use for this plugin to retrieve MySQL metrics. 6 | When deploying the MySQL plugin on the database server, the necessary privileges only require 'localhost' or '127.0.0.1' access. 7 | 8 | CREATE USER newrelic@localhost IDENTIFIED BY PASSWORD '*B8B274C6AF8165B631B4B517BD0ED2694909F464'; 9 | GRANT PROCESS,REPLICATION CLIENT ON *.* TO newrelic@localhost; 10 | CREATE USER newrelic@127.0.0.1 IDENTIFIED BY PASSWORD '*B8B274C6AF8165B631B4B517BD0ED2694909F464'; 11 | GRANT PROCESS,REPLICATION CLIENT ON *.* TO newrelic@127.0.0.1; 12 | 13 | When deploying the MySQL plugin on a common server, i.e. this server will connect one or more MySQL servers over the 14 | network, a necessary change to the SQL statements is required to grant privileges to the applicable host or host mask, 15 | i.e. changing the value of "localhost" in the 2 SQL statements appropriately, e.g. to "10.%" if your deployed server 16 | runs within this IP range. 17 | 18 | A hashed password can be created using the PASSWORD() function. From the MySQL docs: 19 | 20 | mysql> SELECT PASSWORD('mypass'); 21 | +-------------------------------------------+ 22 | | PASSWORD('mypass') | 23 | +-------------------------------------------+ 24 | | *6C8989366EAF75BB670AD8EA7A7FC1176A95CEF4 | 25 | +-------------------------------------------+ 26 | 27 | Customizing the MySQL User Account 28 | ---------------------------------- 29 | 30 | You can use any existing MySQL user account providing the user has the applicable privileges listed above. 31 | You can override all necessary MySQL account details in the MySQL JSON configuration file. The following is an 32 | example syntax of all configurable settings: 33 | 34 | [ 35 | { 36 | "name" : "Prod Server 1", 37 | "host" : "proddb.example.com:3306", 38 | "user" : "newrelic", 39 | "passwd" : "USER_CLEAR_TEXT_PASSWORD_HERE", 40 | "properties" : "?profileSQL=true", 41 | "metrics" : "status,newrelic,slave" 42 | } 43 | ] 44 | 45 | These options are: 46 | 47 | * name - This is a meaningful name for the MySQL server that will show up in the New Relic Dashboard. 48 | * host - This is the hostname of the MySQL server. If necessary a port can be specified. 3306 is the default 49 | and is not required to be stated. 50 | * user - This is the MySQL user account. 51 | * passwd - This is the clear text password for the MySQL user account. 52 | * properties - This is additional Connector/J properties that can be passed to the database connections. This 53 | option is generally left blank. This is an advanced feature. 54 | * metrics - This is the list of metric categories that can be collected by New Relic. The currently accepted 55 | values are: status,master,slave,newrelic. Additional categories can be defined. See CATEGORIES.TXT 56 | for more information. 57 | 58 | Known Issues 59 | ------------ 60 | 61 | * If you are using MySQL 5.1.23 or older you will need to add the SUPER privilege to collect SHOW ENGINE INNODB STATUS. 62 | * While all SQL metric commands should run with MySQL 5.0 and 5.1, due to the end of life policy of these versions, 63 | testing has only been performed with MySQL 5.5 and MySQL 5.6/ 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Archived header](https://github.com/newrelic/open-source-office/raw/master/examples/categories/images/Archived.png)](https://github.com/newrelic/open-source-office/blob/master/examples/categories/index.md#archived) 2 | 3 | *Development of this plugin has discontinued and you are encouraged to migrate your MySQL monitoring solution to the [MySQL integration](https://docs.newrelic.com/docs/integrations/host-integrations/host-integrations-list/mysql-monitoring-integration) for [New Relic Infrastructure](https://docs.newrelic.com/docs/infrastructure).* 4 | 5 | # New Relic Platform MySQL Plugin - Java 6 | 7 | Find the New Relic MySQL plugin in the [New Relic storefront](http://newrelic.com/plugins/new-relic-inc/52) 8 | 9 | Find the New Relic MySQL plugin in [Plugin Central](https://rpm.newrelic.com/extensions/com.newrelic.plugins.mysql.instance) 10 | 11 | The MySQL plugin monitors a variety of metrics provided by MySQL server through query results. Instead of having to type these queries to get performance and functional metrics on your database, the plugin polls the database server on a 1-minute harvest cycle interval and displays the information in a dashboard. 12 | 13 | ---- 14 | 15 | ## General functionality 16 | 17 | In order to poll certain types of metrics the plugin expects to find configuration values pointing out which categories of metrics should be collected. Some of these categories depend on each other, whereas others are only useful in certain contexts. These metric categories are listed as part of the "metrics" key in the plugin.json configuration file for the plugin. The following categories are available: 18 | 19 | Metric Category|Depends On|Default Database Query|Description| 20 | ----------------|------------|------------------------|-------------| 21 | status||SHOW GLOBAL STATUS | General status metrics about the running database server| 22 | master||SHOW MASTER STATUS|Status metrics specific to the "master" server of a master-slave configuration| 23 | slave||SHOW SLAVE STATUS|Status metrics specific to the "slave" server of a master-slave configuration| 24 | newrelic|status||General metrics derived from other (more directly available) ones.| 25 | 26 | ---- 27 | 28 | ## What's new in V2? 29 | 30 | This plugin has been upgraded to V2 of the New Relic Platform Java SDK. For version 2 of the Java SDK, we have made several changes to help make the installation experience more uniform for plugins. The changes include: 31 | 32 | * 'newrelic.properties' file is now 'newrelic.json' 33 | * Plugin configuration is now done through the 'plugin.json' file 34 | * Logging has been made more robust and easier to use. 35 | * Jar distributables now have a well-defined name (i.e. plugin.jar) 36 | * Configuration files are now located in a well-defined location (i.e. './config' off the root) 37 | 38 | More information on these changes (including how to configure logging, license keys, and the plugin itself) can be found [here](https://github.com/newrelic-platform/metrics_publish_java#configuration-options). If you have any feedback, please don't hesitate to reach out to us through our forums [here](https://discuss.newrelic.com/category/platform-plugins/platform-sdk). 39 | 40 | ---- 41 | 42 | ## Requirements 43 | 44 | The requirements for running this plugin are: 45 | 46 | - A New Relic account. Sign up for a free account [here](http://newrelic.com) 47 | - Java Runtime (JRE) environment Version 1.6 or later 48 | - A server running MySQL Version 5.0 or greater 49 | - Network access to New Relic (authenticated proxies are not currently supported, but see workaround below) 50 | 51 | **Note:** The MySQL Plugin includes the [Connector/J JDBC Driver](http://dev.mysql.com/usingmysql/java/) and it does not need to be installed separately. 52 | 53 | ---- 54 | 55 | ## Installation 56 | 57 | This plugin can be installed one of the following ways: 58 | 59 | * [Option 1 - New Relic Platform Installer](#option-1--install-with-the-new-relic-platform-installer) 60 | * [Option 2 - Chef and Puppet Install Scripts](#option-2--install-via-chef-or-puppet) 61 | * [Option 3 - Manual Install](#option-3--install-manually) 62 | 63 | ### Option 1 - Install with the New Relic Platform Installer 64 | 65 | The New Relic Platform Installer (NPI) is a simple, lightweight command line tool that helps you easily download, configure and manage New Relic Platform Plugins. To learn more simply go to [our forum category](https://discuss.newrelic.com/category/platform-plugins/platform-installer) and checkout the ['Getting Started' section](https://discuss.newrelic.com/t/getting-started-for-the-platform-installer/842). If you have any questions, concerns or feedback, please do not hesitate to reach out through the forums as we greatly appreciate your feedback! 66 | 67 | Once you've installed the NPI tool, run the following command: 68 | 69 | ``` 70 | ./npi install com.newrelic.plugins.mysql.instance 71 | ``` 72 | 73 | This command will take care of the creation of `newrelic.json` and `plugin.json` configuration files. See the [configuration information](#configuration-information) section for more information. 74 | 75 | ### Option 2 - Install via Chef or Puppet 76 | 77 | For [Chef](http://www.getchef.com) and [Puppet](http://puppetlabs.com) support see the New Relic plugin's [Chef Cookbook](http://community.opscode.com/cookbooks/newrelic_plugins) and [Puppet Module](https://forge.puppetlabs.com/newrelic/newrelic_plugins). 78 | 79 | Additional information on using Chef and Puppet with New Relic is available in New Relic's [documentation](https://docs.newrelic.com/docs/plugins/plugin-installation-with-chef-and-puppet). 80 | 81 | ### Option 3 - Install Manually (Non-standard) 82 | 83 | #### Step 1 - Downloading and Extracting the Plugin 84 | 85 | The latest version of the plugin can be downloaded [here](https://github.com/newrelic-platform/newrelic_mysql_java_plugin/tree/master/dist). Once the plugin is on your box, extract it to a location of your choosing. 86 | 87 | **note** - This plugin is distributed in tar.gz format and can be extracted with the following command on Unix-based systems (Windows users will need to download a third-party extraction tool or use the [New Relic Platform Installer](https://discuss.newrelic.com/t/getting-started-with-the-platform-installer/842)): 88 | 89 | ``` 90 | tar -xvzf newrelic_mysql_plugin-X.Y.Z.tar.gz 91 | ``` 92 | 93 | #### Step 2 - Configuring the Plugin 94 | 95 | Check out the [configuration information](#configuration-information) section for details on configuring your plugin. 96 | 97 | #### Step 3 - Running the Plugin 98 | 99 | To run the plugin, execute the following command from a terminal or command window (assuming Java is installed and on your path): 100 | 101 | ``` 102 | java -Xmx128m -jar plugin.jar 103 | ``` 104 | 105 | **Note:** Though it is not necessary, the '-Xmx128m' flag is highly recommended due to the fact that when running the plugin on a server class machine, the `java` command will start a JVM that may reserve up to one quarter (25%) of available memory, but the '-Xmx128m' flag will limit heap allocation to a more reasonable 128MBs. 106 | 107 | For more information on JVM server class machines and the `-Xmx` JVM argument, see: 108 | 109 | - [http://docs.oracle.com/javase/6/docs/technotes/guides/vm/server-class.html](http://docs.oracle.com/javase/6/docs/technotes/guides/vm/server-class.html) 110 | - [http://docs.oracle.com/cd/E22289_01/html/821-1274/configuring-the-default-jvm-and-java-arguments.html](http://docs.oracle.com/cd/E22289_01/html/821-1274/configuring-the-default-jvm-and-java-arguments.html) 111 | 112 | #### Step 4 - Keeping the Plugin Running 113 | 114 | Step 3 showed you how to run the plugin; however, there are several problems with running the process directly in the foreground (For example, when the machine reboots the process will not be started again). That said, there are several common ways to keep a plugin running, but they do require more advanced knowledge or additional tooling. We highly recommend considering using the [New Relic Platform Installer](https://discuss.newrelic.com/t/getting-started-with-the-platform-installer/842) or Chef/Puppet scripts for installing plugins as they will take care of most of the heavy lifting for you. 115 | 116 | If you prefer to be more involved in the maintaince of the process, consider one of these tools for managing your plugin process (bear in mind that some of these are OS-specific): 117 | 118 | - [Upstart](http://upstart.ubuntu.com/) 119 | - [Systemd](http://www.freedesktop.org/wiki/Software/systemd/) 120 | - [Runit](http://smarden.org/runit/) 121 | - [Monit](http://mmonit.com/monit/) 122 | 123 | ---- 124 | 125 | ## Configuration Information 126 | 127 | ### Configuration Files 128 | 129 | You will need to modify two configuration files in order to set this plugin up to run. The first (`newrelic.json`) contains configurations used by all Platform plugins (e.g. license key, logging information, proxy settings) and can be shared across your plugins. The second (`plugin.json`) contains data specific to each plugin such as a list of hosts and port combination for what you are monitoring. Templates for both of these files should be located in the '`config`' directory in your extracted plugin folder. 130 | 131 | #### Configuring the `plugin.json` file: 132 | 133 | The `plugin.json` file has a provided template in the `config` directory named `plugin.template.json`. If you are installing manually, make a copy of this template file and rename it to `plugin.json` (the New Relic Platform Installer will automatically handle creation of configuration files for you). 134 | 135 | Below is an example of the `plugin.json` file's contents, you can add multiple objects to the "agents" array to monitor different instances: 136 | 137 | ``` 138 | { 139 | "agents": [ 140 | { 141 | "name" : "Production Master", 142 | "host" : "localhost:port", 143 | "metrics" : "status,newrelic", 144 | "user" : "USER_NAME_HERE", 145 | "passwd" : "USER_CLEAR_TEXT_PASSWORD_HERE" 146 | } 147 | ] 148 | } 149 | ``` 150 | 151 | #### Tips: 152 | 153 | * Set the "name" attribute to match your MySQL databases purpose, e.g. "Production Master" as this will be used to identify that instance in the New Relic UI. 154 | 155 | * If you used the provided [/scripts/mysql_user.sql](https://github.com/newrelic-platform/newrelic_mysql_java_plugin/blob/master/scripts/mysql_user.sql) to generate a default user and password, then you do not need to set the "user" and "passwd" attributes. 156 | 157 | * If using an externally visible IP address, the username and password fields are no longer optional. See [Create a MySQL user (optional)](https://github.com/newrelic-platform/newrelic_mysql_java_plugin/blob/master/README.md#create-a-mysql-user-optional). 158 | 159 | #### Configuring the `newrelic.json` file: 160 | 161 | The `newrelic.json` file also has a provided template in the `config` directory named `newrelic.template.json`. If you are installing manually, make a copy of this template file and rename it to `newrelic.json` (again, the New Relic Platform Installer will automatically handle this for you). 162 | 163 | The `newrelic.json` is a standardized file containing configuration information that applies to any plugin (e.g. license key, logging, proxy settings), so going forward you will be able to copy a single `newrelic.json` file from one plugin to another. Below is a list of the configuration fields that can be managed through this file: 164 | 165 | ##### Configuring your New Relic License Key 166 | 167 | Your New Relic license key is the only required field in the `newrelic.json` file as it is used to determine what account you are reporting to. If you do not know what your license key is, you can learn about it [here](https://newrelic.com/docs/subscriptions/license-key). 168 | 169 | Example: 170 | 171 | ``` 172 | { 173 | "license_key": "YOUR_LICENSE_KEY_HERE" 174 | } 175 | ``` 176 | 177 | ##### Logging configuration 178 | 179 | By default Platform plugins will have their logging turned on; however, you can manage these settings with the following configurations: 180 | 181 | `log_level` - The log level. Valid values: [`debug`, `info`, `warn`, `error`, `fatal`]. Defaults to `info`. 182 | 183 | `log_file_name` - The log file name. Defaults to `newrelic_plugin.log`. 184 | 185 | `log_file_path` - The log file path. Defaults to `logs`. 186 | 187 | `log_limit_in_kbytes` - The log file limit in kilobytes. Defaults to `25600` (25 MB). If limit is set to `0`, the log file size would not be limited. 188 | 189 | Example: 190 | 191 | ``` 192 | { 193 | "license_key": "YOUR_LICENSE_KEY_HERE" 194 | "log_level": "debug", 195 | "log_file_path": "/var/logs/newrelic" 196 | } 197 | ``` 198 | 199 | ##### Proxy configuration 200 | 201 | If you are running your plugin from a machine that runs outbound traffic through a proxy, you can use the following optional configurations in your `newrelic.json` file: 202 | 203 | `proxy_host` - The proxy host (e.g. `webcache.example.com`) 204 | 205 | `proxy_port` - The proxy port (e.g. `8080`). Defaults to `80` if a `proxy_host` is set 206 | 207 | `proxy_username` - The proxy username 208 | 209 | `proxy_password` - The proxy password 210 | 211 | Example: 212 | 213 | ``` 214 | { 215 | "license_key": "YOUR_LICENSE_KEY_HERE", 216 | "proxy_host": "proxy.mycompany.com", 217 | "proxy_port": 9000 218 | } 219 | ``` 220 | 221 | ### Additional Configuration 222 | 223 | #### Create a MySQL user (optional) 224 | 225 | The MySQL plugin requires a MySQL user with limited privileges. To use the New Relic default, run the following SQL script located at [/scripts/mysql_user.sql](https://github.com/newrelic-platform/newrelic_mysql_java_plugin/blob/master/scripts/mysql_user.sql). 226 | 227 | `$ mysql -uroot -p < mysql_user.sql` 228 | 229 | This script will create the following user: 230 | 231 | username: newrelic 232 | host: localhost or 127.0.0.1 233 | password: *B8B274C6AF8165B631B4B517BD0ED2694909F464 (hashed value) 234 | 235 | *You can choose to use a different MySQL user name and password. See [MYSQL.TXT](https://github.com/newrelic-platform/newrelic_mysql_java_plugin/blob/master/MYSQL.TXT) for more info.* 236 | 237 | If your MySQL Server is bound to an externally visible IP address, both localhost and 127.0.0.1 will not be accessible via TCP as the host for the MySQL Plugin. In order for the plugin to connect, you will need to create a user for your IP address. Due to security concerns in this case, we strongly recommend **not** using the default password and instead setting it to some other value. 238 | 239 | CREATE USER newrelic@ IDENTIFIED BY ''; 240 | GRANT PROCESS, REPLICATION CLIENT ON *.* TO newrelic@; 241 | 242 | #### Selecting metrics 243 | 244 | The MySQL Plugin is capable of reporting different sets of metrics by configuring the 'metrics' attribute. E.g., add the 'slave' category to report replication metrics. 245 | *See [CATEGORIES.TXT](https://github.com/newrelic-platform/newrelic_mysql_java_plugin/blob/master/CATEGORIES.TXT) for more info.* 246 | 247 | **Note:** The `innodb_mutex` metric category can lead to increased memory usage for the plugin if the monitored database is under a high level of contention (i.e. large numbers of active mutexes). 248 | 249 | ## Troubleshooting 250 | 251 | ### If you don’t see plugin metrics report to your account: 252 | 253 | When the component simply doesn't report to your account, it's usually indicative of a permissions issue. When this issue occurs, ensure that you have a user with the permissions listed in the repository's additional configuration: 254 | 255 | * [Additional Configuration](https://github.com/newrelic-platform/newrelic_mysql_java_plugin/blob/master/README.md#additional-configuration) 256 | 257 | Other than permissions, verifying that the license key is correct and that the plugin can connect to our collectors through any firewalls are good sanity checks: 258 | 259 | * [Confuguring the `newrelic.json` file](https://github.com/newrelic-platform/newrelic_mysql_java_plugin/blob/master/README.md#configuring-the-pluginjson-file) 260 | 261 | ### If you don't see any data for the 'X' metric: 262 | 263 | Most of the time, a missing metric is a result of a missing category in the plugin.json file. Confirm your setup (i.e. are you using a master->slave architecture?) and verify that the correct categories for that component (slave, master, etc.) are entered into the "metrics" field of the plugin.json file. 264 | 265 | If the correct categories are listed, the issue is almost always a problem with the MySQL server not reporting certain numbers, or not running in a setup where the numbers will ever have anything meaningful to report. 266 | 267 | You may be curious about "Replication Lag" on a slave database. Assuming configuration is correct, replication lag will only be present in a small set of circumstances. Details on this metric and all other metrics the plugin draws from can be found in the official MySQL documentation for the underlying queries. 268 | 269 | * [SHOW STATUS Syntax](https://dev.mysql.com/doc/refman/5.7/en/show-status.html) 270 | 271 | * [SHOW MASTER STATUS Syntax](https://dev.mysql.com/doc/refman/5.7/en/show-master-status.html) 272 | 273 | * [SHOW SLAVE STATUS Syntax](https://dev.mysql.com/doc/refman/5.7/en/show-slave-status.html) 274 | 275 | For further confirmation of the numbers reported for various metrics try collecting detailed logs from your plugin instance and examining them. 276 | 277 | ### Collecting Logs 278 | #### When using NPI: 279 | 280 | 1. Browse to your plugin install directory and edit the `newrelic.json` file, from the NPI folder `plugins/com.newrelic.plugins.mysql.instance/newrelic_mysql_plugin-{version}/config/newrelic.json` 281 | 2. Change the line `log_level": "info"` to `log_level": "debug"` 282 | 3. Save and close the file 283 | 4. Restart the plugin with the commands; `./npi stop com.newrelic.plugins.mysql.instance` then `./npi start com.newrelic.plugins.mysql.instance` 284 | 5. If applicable, the generated logs from `plugins/com.newrelic.plugins.mysql.instance/newrelic_mysql_plugin-{version}/logs/newrelic_plugin.log` can be sent to support 285 | 6. Set the log level back to `info` and restart the plugin again 286 | 287 | 288 | --- 289 | 290 | Once permissions and configuration are taken care of, most problems you encounter will be the result of the underlying database server. Certain metrics always reporting as "0", missing query counts, etc. can be influenced by various aspects of how MySQL is expected to run. 291 | 292 | Additionally, even though Amazon Aurora is built around MySQL, the MySQL plugin will not instrument IOPS due to how IOPS are billed in Amazon RDS. 293 | 294 | ## Support 295 | 296 | Plugin support and troubleshooting assistance can be obtained by visiting [support.newrelic.com](https://support.newrelic.com) 297 | 298 | ### Frequently Asked Questions 299 | 300 | **Q: Does this plugin support Amazon RDS?** 301 | 302 | **A:** The MySQL plugin can report metrics for Amazon RDS MySQL instances as well. To do so, configure the `plugin.json` as mentioned above in [Configure your MySQL properties](#configure-your-mysql-properties) and set your `host` attribute to the RDS instance endpoint without the port. The endpoint can be found in the AWS console for your instance. For more information regarding the RDS endpoint see the [Amazon RDS documentation](http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_ConnectToInstance.html). 303 | 304 | E.g. `database1.abcjiasdocdsf.us-west-1.rds.amazonaws.com` 305 | 306 | The `user` and `passwd` attributes should be the RDS master user and master password, or a user and password that has correct privileges. See [Create MySQL user if necessary](#create-mysql-user-if-necessary) for more information. 307 | 308 | **Q: Do you support MariaDB?** 309 | 310 | **A:** The short answer is 'no'. While we understand that MariaDB is supposed to be functionally equivalent to MySQL, we have received feedback from some users that the MySQL plugin does not work against their MariaDB instances. We may address this at some point in the future, but currently do not actively support this scenario. 311 | 312 | ---- 313 | 314 | ## Fork me! 315 | 316 | This plugin uses an extensible architecture that allows you to define new MySQL metrics beyond the provided defaults. To expose more data about your MySQL servers, fork this repository, create a new GUID, add the metrics you would like to collect to config/metric.category.json and then build summary metrics and dashboards to expose your newly collected metrics. 317 | 318 | *See [CATEGORIES.TXT](https://github.com/newrelic-platform/newrelic_mysql_java_plugin/blob/master/CATEGORIES.TXT) for more info.* 319 | 320 | ---- 321 | 322 | ## Credits 323 | 324 | The MySQL plugin was originally authored by [Ronald Bradford](http://ronaldbradford.com/) of [EffectiveMySQL](http://effectivemysql.com/). Subsequent updates and support are provided by [New Relic](http://newrelic.com/platform). 325 | 326 | ## Contributing 327 | 328 | You are welcome to send pull requests to us - however, by doing so you agree that you are granting New Relic a non-exclusive, non-revokable, no-cost license to use the code, algorithms, patents, and ideas in that code in our products if we so choose. You also agree the code is provided as-is and you provide no warranties as to its fitness or correctness for any purpose. 329 | -------------------------------------------------------------------------------- /build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | Cleaning project... 31 | 32 | Done. 33 | 34 | 35 | 36 | Creating directory: ${build.dir} 37 | 38 | 39 | 40 | 41 | 42 | Building project... 43 | 44 | 45 | 46 | 47 | Done. 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /config/metric.category.json: -------------------------------------------------------------------------------- 1 | [ 2 | { "category" : "status", "SQL" : "SHOW GLOBAL STATUS", "result" : "set", 3 | "value_metrics":"Innodb_page_size,Innodb_buffer_pool_pages_flushed,Max_used_connections,Open_files,Open_streams,Open_table_definitions,Open_tables,Qcache_free_blocks,Qcache_free_memory,Qcache_queries_in_cache,Qcache_total_blocks,Threads_cached,Threads_connected,Threads_created,Threads_running,Uptime", 4 | "counter_metrics" : "Aborted_clients,Aborted_connects,Bytes_received,Bytes_sent,Com_admin_commands,Com_assign_to_keycache,Com_alter_db,Com_alter_db_upgrade,Com_alter_event,Com_alter_function,Com_alter_procedure,Com_alter_server,Com_alter_table,Com_alter_tablespace,Com_alter_user,Com_analyze,Com_begin,Com_binlog,Com_call_procedure,Com_change_db,Com_change_master,Com_check,Com_checksum,Com_commit,Com_create_db,Com_create_event,Com_create_function,Com_create_index,Com_create_procedure,Com_create_server,Com_create_table,Com_create_trigger,Com_create_udf,Com_create_user,Com_create_view,Com_dealloc_sql,Com_delete,Com_delete_multi,Com_do,Com_drop_db,Com_drop_event,Com_drop_function,Com_drop_index,Com_drop_procedure,Com_drop_server,Com_drop_table,Com_drop_trigger,Com_drop_user,Com_drop_view,Com_empty_query,Com_execute_sql,Com_flush,Com_grant,Com_ha_close,Com_ha_open,Com_ha_read,Com_help,Com_insert,Com_insert_select,Com_install_plugin,Com_kill,Com_load,Com_lock_tables,Com_optimize,Com_preload_keys,Com_prepare_sql,Com_purge,Com_purge_before_date,Com_release_savepoint,Com_rename_table,Com_rename_user,Com_repair,Com_replace,Com_replace_select,Com_reset,Com_resignal,Com_revoke,Com_revoke_all,Com_rollback,Com_rollback_to_savepoint,Com_savepoint,Com_select,Com_set_option,Com_signal,Com_show_authors,Com_show_binlog_events,Com_show_binlogs,Com_show_charsets,Com_show_collations,Com_show_contributors,Com_show_create_db,Com_show_create_event,Com_show_create_func,Com_show_create_proc,Com_show_create_table,Com_show_create_trigger,Com_show_databases,Com_show_engine_logs,Com_show_engine_mutex,Com_show_engine_status,Com_show_events,Com_show_errors,Com_show_fields,Com_show_function_status,Com_show_grants,Com_show_keys,Com_show_master_status,Com_show_open_tables,Com_show_plugins,Com_show_privileges,Com_show_procedure_status,Com_show_processlist,Com_show_profile,Com_show_profiles,Com_show_relaylog_events,Com_show_slave_hosts,Com_show_slave_status,Com_show_status,Com_show_storage_engines,Com_show_table_status,Com_show_tables,Com_show_triggers,Com_show_variables,Com_show_warnings,Com_slave_start,Com_slave_stop,Com_stmt_close,Com_stmt_execute,Com_stmt_fetch,Com_stmt_prepare,Com_stmt_reprepare,Com_stmt_reset,Com_stmt_send_long_data,Com_truncate,Com_uninstall_plugin,Com_unlock_tables,Com_update,Com_update_multi,Com_xa_commit,Com_xa_end,Com_xa_prepare,Com_xa_recover,Com_xa_rollback,Com_xa_start,Created_tmp_disk_tables,Created_tmp_tables,Innodb_buffer_pool_read_ahead_rnd,Innodb_buffer_pool_read_ahead,Innodb_buffer_pool_read_ahead_evicted,Innodb_buffer_pool_read_requests,Innodb_buffer_pool_reads,Innodb_buffer_pool_wait_free,Innodb_buffer_pool_write_requests,Innodb_data_fsyncs,Innodb_data_pending_fsyncs,Innodb_data_read,Innodb_data_reads,Innodb_data_writes,Innodb_data_written,Innodb_os_log_fsyncs,Innodb_os_log_written,Innodb_pages_created,Innodb_pages_read,Innodb_pages_written,Innodb_rows_deleted,Innodb_rows_inserted,Innodb_rows_read,Innodb_rows_updated,Max_used_connections,Opened_files,Opened_table_definitions,Opened_tables,Qcache_hits,Qcache_inserts,Qcache_lowmem_prunes,Qcache_not_cached,Select_full_join,Select_full_range_join,Select_range,Select_range_check,Select_scan,Slow_queries,Sort_range,Sort_scan,Table_locks_immediate,Table_locks_waited,Threads_cached,Threads_connected,Threads_created,Threads_running" }, 5 | { "category" : "slave", "SQL" : "SHOW SLAVE STATUS", "result" : "row", 6 | "value_metrics" : "Read_Master_Log_Pos,Slave_IO_Running,Slave_SQL_Running,Exec_Master_Log_Pos,Relay_Log_Pos,Relay_Log_Size,Seconds_Behind_Master,Last_Errno", 7 | "counter_metrics" : "" }, 8 | { "category" : "master", "SQL" : "SHOW MASTER STATUS", "result" : "row", 9 | "counter_metrics" : "Position" } 10 | { "category" : "buffer_pool_stats", "SQL" : "SELECT * FROM information_schema.innodb_buffer_pool_stats", "result" : "row", "comment" : "MySQL 5.5 or later", 11 | "value_metrics" : "", 12 | "counter_metrics" : "", }, 13 | { "category" : "innodb_metrics", "SQL" : "SELECT name, count FROM information_schema.innodb_metrics", "result" : "set", "comment" : "MySQL 5.6 or later", 14 | "value_metrics" : "", 15 | "counter_metrics" : "", }, 16 | { "category" : "innodb_mutex", "SQL" : "SHOW ENGINE INNODB MUTEX", "result" : "special", 17 | "value_metrics" : "", "counter_metrics" : "", }, 18 | { "category" : "innodb_status", "SQL" : "SHOW ENGINE INNODB STATUS", "result" : "special", 19 | "value_metrics" : "", "counter_metrics" : "", }, 20 | ] 21 | -------------------------------------------------------------------------------- /config/newrelic.template.json: -------------------------------------------------------------------------------- 1 | { 2 | "license_key": "YOUR_LICENSE_KEY_HERE", 3 | "log_level": "info", 4 | "log_file_name": "newrelic_plugin.log", 5 | "log_file_path": "logs" 6 | } 7 | -------------------------------------------------------------------------------- /config/plugin.template.json: -------------------------------------------------------------------------------- 1 | { 2 | "agents": [ 3 | { 4 | "name" : "Localhost", 5 | "host" : "localhost", 6 | "metrics" : "status,newrelic", 7 | "user" : "USER_NAME_HERE", 8 | "passwd" : "USER_PASSWD_HERE" 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /dist/newrelic_mysql_plugin-0.8.1-beta.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newrelic/newrelic_mysql_java_plugin/97281abb3bc1b3a5be09f37631741a9769ed5e6e/dist/newrelic_mysql_plugin-0.8.1-beta.tar.gz -------------------------------------------------------------------------------- /dist/newrelic_mysql_plugin-0.8.2-beta.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newrelic/newrelic_mysql_java_plugin/97281abb3bc1b3a5be09f37631741a9769ed5e6e/dist/newrelic_mysql_plugin-0.8.2-beta.tar.gz -------------------------------------------------------------------------------- /dist/newrelic_mysql_plugin-0.8.3-beta.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newrelic/newrelic_mysql_java_plugin/97281abb3bc1b3a5be09f37631741a9769ed5e6e/dist/newrelic_mysql_plugin-0.8.3-beta.tar.gz -------------------------------------------------------------------------------- /dist/newrelic_mysql_plugin-1.0.0.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newrelic/newrelic_mysql_java_plugin/97281abb3bc1b3a5be09f37631741a9769ed5e6e/dist/newrelic_mysql_plugin-1.0.0.tar.gz -------------------------------------------------------------------------------- /dist/newrelic_mysql_plugin-1.0.1.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newrelic/newrelic_mysql_java_plugin/97281abb3bc1b3a5be09f37631741a9769ed5e6e/dist/newrelic_mysql_plugin-1.0.1.tar.gz -------------------------------------------------------------------------------- /dist/newrelic_mysql_plugin-1.0.2.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newrelic/newrelic_mysql_java_plugin/97281abb3bc1b3a5be09f37631741a9769ed5e6e/dist/newrelic_mysql_plugin-1.0.2.tar.gz -------------------------------------------------------------------------------- /dist/newrelic_mysql_plugin-1.0.3.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newrelic/newrelic_mysql_java_plugin/97281abb3bc1b3a5be09f37631741a9769ed5e6e/dist/newrelic_mysql_plugin-1.0.3.tar.gz -------------------------------------------------------------------------------- /dist/newrelic_mysql_plugin-1.0.4.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newrelic/newrelic_mysql_java_plugin/97281abb3bc1b3a5be09f37631741a9769ed5e6e/dist/newrelic_mysql_plugin-1.0.4.tar.gz -------------------------------------------------------------------------------- /dist/newrelic_mysql_plugin-1.0.5.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newrelic/newrelic_mysql_java_plugin/97281abb3bc1b3a5be09f37631741a9769ed5e6e/dist/newrelic_mysql_plugin-1.0.5.tar.gz -------------------------------------------------------------------------------- /dist/newrelic_mysql_plugin-1.0.6.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newrelic/newrelic_mysql_java_plugin/97281abb3bc1b3a5be09f37631741a9769ed5e6e/dist/newrelic_mysql_plugin-1.0.6.tar.gz -------------------------------------------------------------------------------- /dist/newrelic_mysql_plugin-1.0.7.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newrelic/newrelic_mysql_java_plugin/97281abb3bc1b3a5be09f37631741a9769ed5e6e/dist/newrelic_mysql_plugin-1.0.7.tar.gz -------------------------------------------------------------------------------- /dist/newrelic_mysql_plugin-1.0.8.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newrelic/newrelic_mysql_java_plugin/97281abb3bc1b3a5be09f37631741a9769ed5e6e/dist/newrelic_mysql_plugin-1.0.8.tar.gz -------------------------------------------------------------------------------- /dist/newrelic_mysql_plugin-1.0.9.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newrelic/newrelic_mysql_java_plugin/97281abb3bc1b3a5be09f37631741a9769ed5e6e/dist/newrelic_mysql_plugin-1.0.9.tar.gz -------------------------------------------------------------------------------- /dist/newrelic_mysql_plugin-1.1.0.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newrelic/newrelic_mysql_java_plugin/97281abb3bc1b3a5be09f37631741a9769ed5e6e/dist/newrelic_mysql_plugin-1.1.0.tar.gz -------------------------------------------------------------------------------- /dist/newrelic_mysql_plugin-1.2.0.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newrelic/newrelic_mysql_java_plugin/97281abb3bc1b3a5be09f37631741a9769ed5e6e/dist/newrelic_mysql_plugin-1.2.0.tar.gz -------------------------------------------------------------------------------- /dist/newrelic_mysql_plugin-2.0.0.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newrelic/newrelic_mysql_java_plugin/97281abb3bc1b3a5be09f37631741a9769ed5e6e/dist/newrelic_mysql_plugin-2.0.0.tar.gz -------------------------------------------------------------------------------- /lib/metrics_publish-2.0.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newrelic/newrelic_mysql_java_plugin/97281abb3bc1b3a5be09f37631741a9769ed5e6e/lib/metrics_publish-2.0.0.jar -------------------------------------------------------------------------------- /lib/mysql-connector-java-5.1.24-bin.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newrelic/newrelic_mysql_java_plugin/97281abb3bc1b3a5be09f37631741a9769ed5e6e/lib/mysql-connector-java-5.1.24-bin.jar -------------------------------------------------------------------------------- /scripts/etc/default/newrelic-mysql-plugin: -------------------------------------------------------------------------------- 1 | DAEMONDIR=/path/to/newrelic/plugin 2 | -------------------------------------------------------------------------------- /scripts/etc/init.d/newrelic-mysql-plugin.debian: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | ### BEGIN INIT INFO 3 | # Provides: newrelic-mysql-plugin 4 | # Required-Start: $remote_fs $network 5 | # Required-Stop: $remote_fs $network 6 | # Default-Start: 2 3 4 5 7 | # Default-Stop: 0 1 6 8 | # Short-Description: New Relic mysql plugin Debian daemon control script 9 | # Description: Controls the New Relic java mysql plugin 10 | ### END INIT INFO 11 | 12 | # Author: Joe Niland 13 | 14 | # CONFIGURATION 15 | # - Change DAEMONDIR to your chosen location for the plugin jar file. 16 | # - To run on boot, don't forget to run update-rc.d newrelic-mysql-plugin defaults. 17 | 18 | PATH=/sbin:/usr/sbin:/bin:/usr/bin 19 | DESC="New Relic mysql plugin daemon" 20 | NAME=newrelic-mysql-plugin 21 | DAEMON=plugin.jar 22 | DAEMON_ARGS="" 23 | PIDFILE=/run/$NAME.pid 24 | SCRIPTNAME=/etc/init.d/$NAME 25 | JAVA=/usr/bin/java 26 | DAEMON_USER=nobody 27 | DAEMONDIR=/path/to/plugins 28 | 29 | # Read configuration variable file if it is present 30 | [ -r /etc/default/$NAME ] && . /etc/default/$NAME 31 | 32 | # Exit if the package is not installed 33 | if test ! -x $JAVA -o ! -e $DAEMONDIR/$DAEMON 34 | then 35 | echo $JAVA or $DAEMONDIR/$DAEMON do not exist 36 | exit 0 37 | fi 38 | 39 | . /lib/init/vars.sh 40 | . /lib/lsb/init-functions 41 | 42 | do_start() { 43 | pid=$(pidofproc -p $PIDFILE $DAEMON) 44 | if [ -n "$pid" ] ; then 45 | log_begin_msg "$DESC already running." 46 | log_end_msg 0 47 | exit 0 48 | fi 49 | 50 | log_daemon_msg "Starting $DESC: " 51 | # add this if you need to log the output: --no-close 52 | start-stop-daemon --start --quiet --background --make-pidfile --pidfile $PIDFILE --chuid $DAEMON_USER --chdir $DAEMONDIR --exec $JAVA -- -jar $DAEMONDIR/$DAEMON 53 | # append this to the above line if you need to log the output: > /var/log/newrelic-plugin.log 2>&1 54 | log_end_msg $? 55 | } 56 | 57 | do_stop() { 58 | log_begin_msg "Stopping $DESC ..." 59 | start-stop-daemon --stop --quiet --oknodo --pidfile $PIDFILE 60 | RC=$? 61 | [ $RC -eq 0 ] && rm -f $PIDFILE 62 | log_end_msg $RC 63 | } 64 | 65 | case "$1" in 66 | start) 67 | [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME" 68 | do_start 69 | case "$?" in 70 | 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; 71 | 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; 72 | esac 73 | ;; 74 | stop) 75 | [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" 76 | do_stop 77 | case "$?" in 78 | 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; 79 | 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; 80 | esac 81 | ;; 82 | status) 83 | status_of_proc -p "$PIDFILE" "$DAEMON" "$NAME" && exit 0 || exit $? 84 | ;; 85 | #reload) 86 | # not implemented 87 | #;; 88 | restart|force-reload) 89 | log_daemon_msg "Restarting $DESC" "$NAME" 90 | do_stop 91 | case "$?" in 92 | 0|1) 93 | do_start 94 | case "$?" in 95 | 0) log_end_msg 0 ;; 96 | 1) log_end_msg 1 ;; # Old process is still running 97 | *) log_end_msg 1 ;; # Failed to start 98 | esac 99 | ;; 100 | *) 101 | # Failed to stop 102 | log_end_msg 1 103 | ;; 104 | esac 105 | ;; 106 | *) 107 | echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2 108 | exit 3 109 | ;; 110 | esac 111 | 112 | exit 0 113 | -------------------------------------------------------------------------------- /scripts/etc/init.d/newrelic-mysql-plugin.rh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # 4 | # chkconfig: 2345 80 20 5 | # description: Starts and stops the New Relic MySQL Plugin 6 | # processname: java-newrelic-mysql-plugin 7 | 8 | 9 | # Source function library. 10 | FUNCTIONS="/etc/init.d/functions" 11 | [ -s "${FUNCTIONS}" ] && . ${FUNCTIONS} 12 | 13 | # Program Specific Variables 14 | PROGRAM="newrelic-mysql-plugin" 15 | LOCK_FILE="/var/lock/subsys/${PROGRAM}" 16 | PID_FILE="/var/run/${PROGRAM}.pid" 17 | 18 | # Set this to the plugin directory 19 | [ -z "${PLUGIN_DIR}" ] && PLUGIN_DIR="/usr/local/newrelic-mysql" 20 | 21 | # Logging 22 | [ -z "${LOG_DIR}" ] && LOG_DIR="/var/log" 23 | LOG_FILE="${LOG_DIR}/${PROGRAM}.log" 24 | 25 | # Java Process 26 | JAVA=`which java 2>/dev/null` 27 | [ -z "${JAVA}" ] && echo "java not found in the PATH" && exit 1 28 | 29 | # Plugin Location verification 30 | [ -z "${PLUGIN_DIR}" ] && echo "PLUGIN_DIR must be defined" && exit 2 31 | [ ! -d "${PLUGIN_DIR}" ] && echo "PLUGIN_DIR '${PLUGIN_DIR}' is not a directory" && exit 3 32 | 33 | # New Relic MySQL Jar verification 34 | cd ${PLUGIN_DIR} 35 | JAR=`ls *.jar 2>/dev/null | head -1` 36 | [ -z "${JAR}" ] && echo "No New Relic jar found in '${PLUGIN_DIR}'" && exit 4 37 | 38 | start() { 39 | # Start daemons. 40 | echo -n $"Starting ${PROGRAM}: " 41 | nohup ${JAVA} -jar ${JAR} > ${LOG_FILE} 2>&1 & 42 | RETVAL=$? 43 | echo $! > ${PID_FILE} 44 | echo 45 | [ $RETVAL -eq 0 ] && touch ${LOCK_FILE} 46 | return $RETVAL 47 | } 48 | 49 | stop() { 50 | echo -n $"Shutting down ${PROGRAM}: " 51 | [ ! -s "${PID_FILE}" ] && echo "No pid file found" && exit 1 52 | PID=`cat ${PID_FILE}` 53 | #killproc -p ${PID} 54 | kill -9 ${PID} > /dev/null 2>&1 55 | RETVAL=$? 56 | echo 57 | [ $RETVAL -eq 0 ] && rm -f ${LOCK_FILE} ${PID_FILE} 58 | return $RETVAL 59 | } 60 | 61 | # See how we were called. 62 | case "$1" in 63 | start) 64 | start 65 | ;; 66 | stop) 67 | stop 68 | ;; 69 | status) 70 | status $PROGRAM 71 | ;; 72 | restart|force-reload) 73 | stop 74 | start 75 | ;; 76 | try-restart|condrestart) 77 | if status $PROGRAM > /dev/null; then 78 | stop 79 | start 80 | fi 81 | ;; 82 | reload) 83 | exit 3 84 | ;; 85 | *) 86 | echo $"Usage: $0 {start|stop|status|restart|try-restart|force-reload}" 87 | exit 2 88 | esac 89 | -------------------------------------------------------------------------------- /scripts/etc/init/newrelic-mysql-plugin.conf: -------------------------------------------------------------------------------- 1 | # /etc/init/newrelic-mysql-plugin.conf 2 | 3 | description "New Relic mysql plugin daemon" 4 | author "Soban Vuex" 5 | 6 | env DAEMONDIR=/path/to/plugin 7 | env DAEMON=plugin.jar 8 | env JAVA=/usr/bin/java 9 | 10 | start on runlevel [2345] 11 | stop on runlevel [!2345] 12 | 13 | expect fork 14 | respawn 15 | respawn limit 5 5 16 | 17 | script 18 | chdir $DAEMONDIR 19 | exec $JAVA -jar $DAEMON &2>> /dev/null 20 | end script 21 | -------------------------------------------------------------------------------- /scripts/mysql_user.sql: -------------------------------------------------------------------------------- 1 | CREATE USER newrelic@localhost IDENTIFIED BY PASSWORD '*B8B274C6AF8165B631B4B517BD0ED2694909F464'; 2 | GRANT PROCESS,REPLICATION CLIENT ON *.* TO newrelic@localhost; 3 | CREATE USER newrelic@127.0.0.1 IDENTIFIED BY PASSWORD '*B8B274C6AF8165B631B4B517BD0ED2694909F464'; 4 | GRANT PROCESS,REPLICATION CLIENT ON *.* TO newrelic@127.0.0.1; 5 | -------------------------------------------------------------------------------- /src/com/newrelic/plugins/mysql/MetricMeta.java: -------------------------------------------------------------------------------- 1 | package com.newrelic.plugins.mysql; 2 | 3 | import static com.newrelic.plugins.mysql.util.Constants.*; 4 | 5 | import com.newrelic.metrics.publish.processors.EpochCounter; 6 | 7 | /** 8 | * This class holds additional meta data about a given metric. This enables the 9 | * Agent to work more intelligently with the global MySQL commands, by default 10 | * knowing nothing, but then being able to define better unit names, identifying 11 | * counters etc 12 | * 13 | * Currently a Metric can have the following attributes 14 | * 15 | * - Counter (Yes/No). The default is Yes - Unit Name 16 | * 17 | * @author Ronald Bradford me@ronaldbradford.com 18 | * 19 | */ 20 | public class MetricMeta { 21 | 22 | public final static String DEFAULT_UNIT = "Operations"; 23 | public final static String DEFAULT_COUNTER_UNIT = DEFAULT_UNIT + "/Second"; 24 | 25 | private final String unit; 26 | private EpochCounter counter = null; 27 | 28 | public MetricMeta(boolean isCounter, String unit) { 29 | this.unit = unit; 30 | if (isCounter) { 31 | this.counter = new EpochCounter(); 32 | } 33 | } 34 | 35 | public MetricMeta(boolean isCounter) { 36 | this.unit = isCounter ? DEFAULT_COUNTER_UNIT : DEFAULT_UNIT; 37 | if (isCounter) { 38 | this.counter = new EpochCounter(); 39 | } 40 | } 41 | 42 | public static MetricMeta defaultMetricMeta() { 43 | return new MetricMeta(true); 44 | } 45 | 46 | public boolean isCounter() { 47 | return (this.counter == null ? false : true); 48 | } 49 | 50 | public String getUnit() { 51 | return this.unit; 52 | } 53 | 54 | public EpochCounter getCounter() { 55 | return this.counter; 56 | } 57 | 58 | @Override 59 | public String toString() { 60 | return new StringBuilder() 61 | .append(isCounter() ? COUNTER : EMPTY_STRING) 62 | .append(LEFT_PAREN) 63 | .append(getUnit()) 64 | .append(RIGHT_PAREN) 65 | .toString(); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/com/newrelic/plugins/mysql/MySQL.java: -------------------------------------------------------------------------------- 1 | package com.newrelic.plugins.mysql; 2 | 3 | import static com.newrelic.plugins.mysql.util.Constants.*; 4 | 5 | import java.sql.Connection; 6 | import java.sql.DriverManager; 7 | import java.sql.ResultSet; 8 | import java.sql.ResultSetMetaData; 9 | import java.sql.SQLException; 10 | import java.sql.Statement; 11 | import java.util.Arrays; 12 | import java.util.Collections; 13 | import java.util.HashMap; 14 | import java.util.HashSet; 15 | import java.util.Map; 16 | import java.util.Set; 17 | 18 | import com.newrelic.metrics.publish.util.Logger; 19 | 20 | /** 21 | * This class provide MySQL specific methods, operations and values for New Relic Agents reporting MySQL Metrics 22 | * 23 | * @author Ronald Bradford me@ronaldbradford.com 24 | * 25 | */ 26 | public class MySQL { 27 | 28 | private static final Logger logger = Logger.getLogger(MySQL.class); 29 | 30 | private Connection conn = null; // Cached Database Connection 31 | private boolean connectionInitialized = false; 32 | 33 | public MySQL() { 34 | } 35 | 36 | /** 37 | * This method will return a new MySQL database connection 38 | * 39 | * @param host String Hostname for MySQL Connection 40 | * @param user String Database username for MySQL Connection 41 | * @param passwd String database password for MySQL Connection 42 | * @return connection new MySQL Connection 43 | */ 44 | private Connection getNewConnection(String host, String user, String passwd, String properties) { 45 | Connection newConn = null; 46 | String dbURL = buildString(JDBC_URL, host, SLASH, properties); 47 | String connectionInfo = buildString(dbURL, SPACE, user, PASSWORD_FILTERED); 48 | 49 | logger.debug("Getting new MySQL Connection: ", connectionInfo); 50 | 51 | try { 52 | if (!connectionInitialized) { 53 | // load jdbc driver 54 | Class.forName("com.mysql.jdbc.Driver").newInstance(); 55 | connectionInitialized = true; 56 | } 57 | newConn = DriverManager.getConnection(dbURL, user, passwd); 58 | if (newConn == null) { 59 | logger.error("Unable to obtain a new database connection: ", connectionInfo, ", check your MySQL configuration settings."); 60 | } 61 | } catch (Exception e) { 62 | logger.error("Unable to obtain a new database connection: ", connectionInfo, ", check your MySQL configuration settings. ", e.getMessage()); 63 | } 64 | return newConn; 65 | } 66 | 67 | /** 68 | * This method will return a MySQL database connection for use, either a new connection or a cached connection 69 | * 70 | * @param host String Hostname 71 | * @param user String Database username 72 | * @param passwd String database password 73 | * @return A MySQL Database connection for use 74 | */ 75 | public Connection getConnection(String host, String user, String passwd, String properties) { 76 | if (conn == null) { 77 | conn = getNewConnection(host, user, passwd, properties); 78 | } 79 | // Test Connection, and reconnect if necessary 80 | else if (!isConnectionValid()) { 81 | closeConnection(); 82 | conn = getNewConnection(host, user, passwd, properties); 83 | } 84 | return conn; 85 | } 86 | 87 | /** 88 | * Check if connection is valid by pinging MySQL server. If connection is null or invalid return false, otherwise true. 89 | * 90 | * @return the state of the connection 91 | */ 92 | private boolean isConnectionValid() { 93 | boolean available = false; 94 | if (conn != null) { 95 | Statement stmt = null; 96 | ResultSet rs = null; 97 | try { 98 | logger.debug("Checking connection - pinging MySQL server"); 99 | stmt = conn.createStatement(); 100 | rs = stmt.executeQuery(PING); 101 | available = true; 102 | } catch (SQLException e) { 103 | logger.debug("The MySQL connection is not available."); 104 | available = false; 105 | } finally { 106 | try { 107 | if (stmt != null) { 108 | stmt.close(); 109 | } 110 | if (rs != null) { 111 | rs.close(); 112 | } 113 | } catch (SQLException e) { 114 | logger.debug(e, "Error closing statement/result set: "); 115 | } 116 | rs = null; 117 | stmt = null; 118 | } 119 | } 120 | return available; 121 | } 122 | 123 | /** 124 | * Close current connection 125 | */ 126 | private void closeConnection() { 127 | if (conn != null) { 128 | try { 129 | conn.close(); 130 | conn = null; 131 | } catch (SQLException e) { 132 | logger.debug(e, "Error closing connection: "); 133 | } 134 | } 135 | } 136 | 137 | /** 138 | * 139 | * This method will execute the given SQL Statement and produce a set of key/value pairs that are used for reporting metrics. This method is optimized for 140 | * queries designed to produce New Relic compatible type results 141 | * 142 | * @param c Connection 143 | * @param SQL String of SQL Statement to execute 144 | * @return Map of key/value pairs 145 | */ 146 | public static Map runSQL(Connection c, String category, String SQL, String type) { 147 | Statement stmt = null; 148 | ResultSet rs = null; 149 | Map results = new HashMap(); 150 | 151 | try { 152 | logger.debug("Running SQL Statement ", SQL); 153 | stmt = c.createStatement(); 154 | rs = stmt.executeQuery(SQL); // Execute the given SQL statement 155 | ResultSetMetaData md = rs.getMetaData(); // Obtain Meta data about the SQL query (column names etc) 156 | 157 | if (ROW.equals(type)) { // If we expect a single row of results 158 | if (rs.next()) { 159 | for (int i = 1; i <= md.getColumnCount(); i++) { // use column names as the "key" 160 | String value = transformStringMetric(rs.getString(i)); 161 | String columnName = md.getColumnName(i).toLowerCase(); 162 | if (validMetricValue(value)) { 163 | String key = buildString(category, SEPARATOR, columnName); 164 | results.put(key, translateStringToNumber(value)); 165 | } 166 | // rs.getString converts the string NULL into null 167 | if (SECONDS_BEHIND_MASTER.equals(columnName)) { 168 | if (value == null) { 169 | String key = buildString(category, SEPARATOR, columnName); 170 | results.put(key, -1.0f); 171 | } 172 | } 173 | } 174 | } 175 | } else if (SET.equals(type)) { // This SQL statement return a key/value pair set of rows 176 | if (md.getColumnCount() < 2) { 177 | return results; // If there are less than 2 columns, the resultset is incomplete 178 | } 179 | while (rs.next()) { 180 | String value = transformStringMetric(rs.getString(2)); 181 | if (validMetricValue(value)) { 182 | String key = buildString(category, SEPARATOR, rs.getString(1).toLowerCase()); 183 | results.put(key, translateStringToNumber(value)); 184 | } 185 | } // If there are more than 2 columns, disregard additional columns 186 | 187 | } else if (SPECIAL.equals(type)) { // These are per case bases SQL type commands with special needs 188 | if (SHOW_ENGINE_INNODB_MUTEX.equals(SQL)) { 189 | results.putAll(processInnodbMutex(rs, category)); 190 | } else if (SHOW_ENGINE_INNODB_STATUS.equals(SQL)) { 191 | results.putAll(processInnoDBStatus(rs, category)); 192 | } 193 | } 194 | return results; 195 | } catch (SQLException e) { 196 | logger.error("An SQL error occured running '", SQL, "' ", e.getMessage()); 197 | } finally { 198 | try { 199 | if (rs != null) { 200 | rs.close(); // Release objects 201 | } 202 | if (stmt != null) { 203 | stmt.close(); 204 | } 205 | } catch (SQLException e) { 206 | ; 207 | } 208 | rs = null; 209 | stmt = null; 210 | } 211 | return results; 212 | } 213 | 214 | /** 215 | * This method is special processing for the the SHOW ENGINE INNODB MUTEX output including - Discard first column - String conversion of names - extraction 216 | * of value from column - Aggregation of repeating name rows 217 | * 218 | * 219 | * @param ResultSet rs 220 | * @param String category 221 | * @return Map of metrics collated 222 | * @throws SQLException 223 | */ 224 | private static Map processInnodbMutex(ResultSet rs, String category) throws SQLException { 225 | String mutex; 226 | Float value; 227 | Map mutexes = new HashMap(); 228 | 229 | while (rs.next()) { 230 | mutex = buildString(category, SEPARATOR, rs.getString(2).replaceAll(INNODB_MUTEX_REGEX, EMPTY_STRING).replaceAll(ARROW, UNDERSCORE)); 231 | value = translateStringToNumber(rs.getString(3).substring(rs.getString(3).indexOf(EQUALS) + 1)); 232 | if (mutexes.containsKey(mutex)) { 233 | logger.debug("appending ", value); 234 | value = value + mutexes.get(mutex); 235 | } 236 | mutexes.put(mutex, value); 237 | } 238 | logger.debug("Mutexes: ", mutexes); 239 | 240 | return mutexes; 241 | } 242 | 243 | /** 244 | * This method is special processing for the SHOW ENGINE INNODB STATUS output as this is one blob of text 245 | * 246 | * @param ResultSet rs 247 | * @param String category 248 | * @return Map of metrics collected 249 | * @throws SQLException 250 | */ 251 | public static Map processInnoDBStatus(ResultSet rs, String category) throws SQLException { 252 | if (!rs.next()) { 253 | return Collections.emptyMap(); 254 | } else { 255 | return processInnoDBStatus(rs.getString(3), category); 256 | } 257 | } 258 | 259 | static Map processInnoDBStatus(String status, String category) { 260 | 261 | Set lines = new HashSet(Arrays.asList(status.split(NEW_LINE))); 262 | 263 | Map results = new HashMap(); 264 | Float log_sequence_number = 0.0f, last_checkpoint = 0.0f; 265 | 266 | logger.debug("Processing ", lines.size(), " of SHOW ENGINE INNODB STATUS"); 267 | 268 | for (String s : lines) { 269 | if (s.startsWith(HISTORY_LIST_LENGTH)) { 270 | results.put(buildString(category, SEPARATOR, HISTORY_LIST_LENGTH_METRIC), 271 | translateStringToNumber(s.substring(HISTORY_LIST_LENGTH.length() + 1))); 272 | } else if (s.startsWith(LOG_SEQUENCE_NUMBER)) { 273 | log_sequence_number = translateStringToNumber(s.substring(LOG_SEQUENCE_NUMBER.length() + 1)); 274 | results.put(buildString(category, SEPARATOR, LOG_SEQUENCE_NUMBER_METRIC), log_sequence_number); 275 | } else if (s.startsWith(LAST_CHECKPOINT_AT)) { 276 | last_checkpoint = translateStringToNumber(s.substring(LAST_CHECKPOINT_AT.length() + 1)); 277 | results.put(buildString(category, SEPARATOR, LAST_CHECKPOINT_METRIC), last_checkpoint); 278 | } else if (QUERIES_INSIDE_INNODB_REGEX_PATTERN.matcher(s).matches()) { 279 | results.put(buildString(category, SEPARATOR, QUERIES_INSIDE_INNODB_METRIC), 280 | translateStringToNumber(s.replaceAll(QUERIES_INSIDE_INNODB_REGEX2, EMPTY_STRING))); 281 | results.put(buildString(category, SEPARATOR, QUERIES_IN_QUEUE), translateStringToNumber(s.replaceAll(QUERIES_IN_QUEUE_REGEX, EMPTY_STRING) 282 | .replaceAll(QUERIES_IN_QUEUE_REGEX2, EMPTY_STRING))); 283 | } 284 | } 285 | results.put(buildString(category, SEPARATOR, CHECKPOINT_AGE_METRIC), log_sequence_number - last_checkpoint); 286 | 287 | logger.debug(results); 288 | 289 | return results; 290 | } 291 | 292 | /** 293 | * This method will convert the provided string into a Number (either int or float) 294 | * 295 | * @param String value to convert 296 | * @return Number A int or float representation of the provided string 297 | */ 298 | public static Float translateStringToNumber(String val) { 299 | try { 300 | if (val.contains(SPACE)) { 301 | val = SPACE_PATTERN.matcher(val).replaceAll(EMPTY_STRING); // Strip any spaces 302 | } 303 | return Float.parseFloat(val); 304 | } catch (Exception e) { 305 | logger.error("Unable to parse int/float number from value ", val); 306 | } 307 | return 0.0f; 308 | } 309 | 310 | /** 311 | * Perform some preliminary transformation of string values that can be represented in integer values for monitoring 312 | * 313 | * @param val String value to evaluate 314 | * @return String value that best represents and integer 315 | */ 316 | static String transformStringMetric(String val) { 317 | if (ON.equalsIgnoreCase(val) || TRUE.equalsIgnoreCase(val)) return ONE; // Convert some TEXT metrics into numerics 318 | if (OFF.equalsIgnoreCase(val) || NONE.equalsIgnoreCase(val)) return ZERO; 319 | if (YES.equalsIgnoreCase(val)) return ONE; // For slave/slave_*_running 320 | if (NO.equalsIgnoreCase(val)) return ZERO; // For slave/slave_*_running 321 | if (NULL.equalsIgnoreCase(val)) return NEG_ONE; // For slave/seconds_behind_master 322 | return val; 323 | } 324 | 325 | /** 326 | * Check if the value is a valid New Relic Metric value 327 | * 328 | * @param val String to validate 329 | * @return TRUE if string is a numeric supported by New Relic 330 | */ 331 | static boolean validMetricValue(String val) { 332 | if (val == null || EMPTY_STRING.equals(val)) { 333 | return false; 334 | } 335 | if (VALID_METRIC_PATTERN.matcher(val).matches()) { 336 | return true; 337 | } 338 | return false; 339 | } 340 | 341 | static String buildString(String... strings) { 342 | StringBuilder builder = new StringBuilder(50); 343 | for (String string : strings) { 344 | builder.append(string); 345 | } 346 | return builder.toString(); 347 | } 348 | } 349 | -------------------------------------------------------------------------------- /src/com/newrelic/plugins/mysql/instance/Main.java: -------------------------------------------------------------------------------- 1 | package com.newrelic.plugins.mysql.instance; 2 | 3 | import com.newrelic.metrics.publish.Runner; 4 | import com.newrelic.metrics.publish.configuration.ConfigurationException; 5 | 6 | /** 7 | * This is the main calling class for a New Relic Agent. This class sets up the 8 | * necessary agents from the provided configuration and runs these indefinitely. 9 | * 10 | * @author Ronald Bradford me@ronaldbradford.com 11 | * 12 | */ 13 | public class Main { 14 | 15 | public static void main(String[] args) { 16 | try { 17 | Runner runner = new Runner(); 18 | runner.add(new MySQLAgentFactory()); 19 | runner.setupAndRun(); // Never returns 20 | } catch (ConfigurationException e) { 21 | System.err.println("ERROR: " + e.getMessage()); 22 | System.exit(1); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/com/newrelic/plugins/mysql/instance/MySQLAgent.java: -------------------------------------------------------------------------------- 1 | package com.newrelic.plugins.mysql.instance; 2 | 3 | import static com.newrelic.plugins.mysql.util.Constants.*; 4 | 5 | import java.sql.Connection; 6 | import java.util.Arrays; 7 | import java.util.HashMap; 8 | import java.util.HashSet; 9 | import java.util.Iterator; 10 | import java.util.Map; 11 | import java.util.Set; 12 | 13 | import com.newrelic.metrics.publish.Agent; 14 | import com.newrelic.metrics.publish.util.Logger; 15 | import com.newrelic.plugins.mysql.MetricMeta; 16 | import com.newrelic.plugins.mysql.MySQL; 17 | 18 | /** 19 | * This class creates a specific MySQL agent that is used to obtain a MySQL database connection, 20 | * gather requested metrics and report to New Relic 21 | * 22 | * @author Ronald Bradford me@ronaldbradford.com 23 | * 24 | */ 25 | public class MySQLAgent extends Agent { 26 | 27 | private static final Logger logger = Logger.getLogger(MySQLAgent.class); 28 | 29 | private static final String GUID = "com.newrelic.plugins.mysql.instance"; 30 | private static final String version = "2.0.0"; 31 | 32 | public static final String AGENT_DEFAULT_HOST = "localhost"; // Default values for MySQL Agent 33 | public static final String AGENT_DEFAULT_USER = "newrelic"; 34 | public static final String AGENT_DEFAULT_PASSWD = "f63c225f4abe9e13"; 35 | public static final String AGENT_DEFAULT_PROPERTIES = ""; 36 | public static final String AGENT_DEFAULT_METRICS = "status,newrelic"; 37 | 38 | private final String name; // Agent Name 39 | 40 | private final String host; // MySQL Connection parameters 41 | private final String user; 42 | private final String passwd; 43 | private final String properties; 44 | private String agentInfo; 45 | 46 | private final Set metrics; 47 | // Definition of MySQL meta data (counter, unit, type etc) 48 | private final Map metricsMeta = new HashMap(); 49 | // Definition of categories of metrics 50 | private Map metricCategories = new HashMap(); 51 | 52 | private final MySQL m; // Per agent MySQL Object 53 | 54 | private boolean firstReport = true; 55 | 56 | /** 57 | * Default constructor to create a new MySQL Agent 58 | * 59 | * @param map 60 | * 61 | * @param String Human name for Agent 62 | * @param String MySQL Instance host:port 63 | * @param String MySQL user 64 | * @param String MySQL user password 65 | * @param String CSVm List of metrics to be monitored 66 | */ 67 | public MySQLAgent(String name, String host, String user, String passwd, String properties, Set metrics, Map metricCategories) { 68 | super(GUID, version); 69 | 70 | this.name = name; // Set local attributes for new class object 71 | this.host = host; 72 | this.user = user; 73 | this.passwd = passwd; 74 | this.properties = properties; 75 | 76 | this.metrics = metrics; 77 | this.metricCategories = metricCategories; 78 | 79 | this.m = new MySQL(); 80 | 81 | createMetaData(); // Define incremental counters that are value/sec etc 82 | 83 | logger.debug("MySQL Agent initialized: ", formatAgentParams(name, host, user, properties, metrics)); 84 | } 85 | 86 | /** 87 | * Format Agent parameters for logging 88 | * 89 | * @param name 90 | * @param host 91 | * @param user 92 | * @param properties 93 | * @param metrics 94 | * @return A formatted String representing the Agent parameters 95 | */ 96 | private String formatAgentParams(String name, String host, String user, String properties, Set metrics) { 97 | StringBuilder builder = new StringBuilder(); 98 | builder.append("name: ").append(name).append(" | "); 99 | builder.append("host: ").append(host).append(" | "); 100 | builder.append("user: ").append(user).append(" | "); 101 | builder.append("properties: ").append(properties).append(" | "); 102 | builder.append("metrics: ").append(metrics).append(" | "); 103 | return builder.toString(); 104 | } 105 | 106 | /** 107 | * This method is run for every poll cycle of the Agent. Get a MySQL Database connection and gather metrics. 108 | */ 109 | @Override 110 | public void pollCycle() { 111 | Connection c = m.getConnection(host, user, passwd, properties); // Get a database connection (which should be cached) 112 | if (c == null) { 113 | return; // Unable to continue without a valid database connection 114 | } 115 | 116 | logger.debug("Gathering MySQL metrics. ", getAgentInfo()); 117 | 118 | Map results = gatherMetrics(c); // Gather defined metrics 119 | reportMetrics(results); // Report Metrics to New Relic 120 | firstReport = false; 121 | } 122 | 123 | /** 124 | * This method runs the varies categories of MySQL statements and gathers the metrics that can be reported 125 | * 126 | * @param Connection c MySQL Database Connection 127 | * @param String List of metrics to be obtained for this agent 128 | * @return Map of metrics and values 129 | */ 130 | private Map gatherMetrics(Connection c) { 131 | Map results = new HashMap(); // Create an empty set of results 132 | Map categories = getMetricCategories(); // Get current Metric Categories 133 | 134 | Iterator iter = categories.keySet().iterator(); 135 | while (iter.hasNext()) { 136 | String category = iter.next(); 137 | @SuppressWarnings("unchecked") 138 | Map attributes = (Map) categories.get(category); 139 | if (isReportingForCategory(category)) { 140 | results.putAll(MySQL.runSQL(c, category, attributes.get(SQL), attributes.get(RESULT))); 141 | } 142 | } 143 | results.putAll(newRelicMetrics(results)); 144 | return results; 145 | } 146 | 147 | /** 148 | * This method creates a number of custom New Relic Metrics, that are derived from raw MySQL status metrics 149 | * 150 | * @param Map existing Gathered MySQL metrics 151 | * @param metrics String of the Metric Categories to capture 152 | * @return Map Additional derived metrics 153 | */ 154 | protected Map newRelicMetrics(Map existing) { 155 | Map derived = new HashMap(); 156 | 157 | if (!isReportingForCategory(NEW_RELIC_CATEGORY)) { 158 | return derived; // Only calculate newrelic category if specified. 159 | } 160 | if (!isReportingForCategory(STATUS_CATEGORY)) { 161 | return derived; // "status" category is a pre-requisite for newrelic metrics 162 | } 163 | 164 | logger.debug("Adding New Relic derived metrics"); 165 | 166 | /* read and write volume */ 167 | if (areRequiredMetricsPresent("Reads", existing, "status/com_select", "status/qcache_hits")) { 168 | derived.put("newrelic/volume_reads", existing.get("status/com_select") + existing.get("status/qcache_hits")); 169 | } 170 | 171 | if (areRequiredMetricsPresent("Writes", existing, "status/com_insert", "status/com_update", "status/com_delete", "status/com_replace", 172 | "status/com_insert_select", "status/com_update_multi", "status/com_delete_multi", "status/com_replace_select")) { 173 | derived.put("newrelic/volume_writes", existing.get("status/com_insert") + existing.get("status/com_insert_select") 174 | + existing.get("status/com_update") + existing.get("status/com_update_multi") 175 | + existing.get("status/com_delete") + existing.get("status/com_delete_multi") 176 | + existing.get("status/com_replace") + existing.get("status/com_replace_select")); 177 | } 178 | 179 | /* read and write throughput */ 180 | if (areRequiredMetricsPresent("Read Throughput", existing, "status/bytes_sent")) { 181 | derived.put("newrelic/bytes_reads", existing.get("status/bytes_sent")); 182 | } 183 | 184 | if (areRequiredMetricsPresent("Write Throughput", existing, "status/bytes_received")) { 185 | derived.put("newrelic/bytes_writes", existing.get("status/bytes_received")); 186 | } 187 | 188 | /* Connection management */ 189 | if (areRequiredMetricsPresent("Connection Management", existing, "status/threads_connected", "status/threads_running", "status/threads_cached")) { 190 | Float threads_connected = existing.get("status/threads_connected"); 191 | Float threads_running = existing.get("status/threads_running"); 192 | 193 | derived.put("newrelic/connections_connected", threads_connected); 194 | derived.put("newrelic/connections_running", threads_running); 195 | derived.put("newrelic/connections_cached", existing.get("status/threads_cached")); 196 | 197 | Float pct_connection_utilization = 0.0f; 198 | if (threads_connected > 0) { 199 | pct_connection_utilization = (threads_running / threads_connected) * 100.0f; 200 | } 201 | derived.put("newrelic/pct_connection_utilization", pct_connection_utilization); 202 | } 203 | 204 | /* InnoDB Metrics */ 205 | if (areRequiredMetricsPresent("InnoDB", existing, "status/innodb_pages_created", "status/innodb_pages_read", "status/innodb_pages_written", 206 | "status/innodb_buffer_pool_read_requests", "status/innodb_buffer_pool_reads", "status/innodb_data_fsyncs", "status/innodb_os_log_fsyncs")) { 207 | derived.put("newrelic/innodb_bp_pages_created", existing.get("status/innodb_pages_created")); 208 | derived.put("newrelic/innodb_bp_pages_read", existing.get("status/innodb_pages_read")); 209 | derived.put("newrelic/innodb_bp_pages_written", existing.get("status/innodb_pages_written")); 210 | 211 | /* Innodb Specific Metrics */ 212 | Float innodb_read_requests = existing.get("status/innodb_buffer_pool_read_requests"); 213 | Float innodb_reads = existing.get("status/innodb_buffer_pool_reads"); 214 | 215 | Float pct_innodb_buffer_pool_hit_ratio = 0.0f; 216 | if (innodb_read_requests + innodb_reads > 0) { 217 | pct_innodb_buffer_pool_hit_ratio = (innodb_read_requests / (innodb_read_requests + innodb_reads)) * 100.0f; 218 | } 219 | 220 | derived.put("newrelic/pct_innodb_buffer_pool_hit_ratio", pct_innodb_buffer_pool_hit_ratio); 221 | derived.put("newrelic/innodb_fsyncs_data", existing.get("status/innodb_data_fsyncs")); 222 | derived.put("newrelic/innodb_fsyncs_os_log", existing.get("status/innodb_os_log_fsyncs")); 223 | } 224 | 225 | /* InnoDB Buffer Metrics */ 226 | if (areRequiredMetricsPresent("InnoDB Buffers", existing, "status/innodb_buffer_pool_pages_total", "status/innodb_buffer_pool_pages_data", 227 | "status/innodb_buffer_pool_pages_misc", "status/innodb_buffer_pool_pages_dirty", "status/innodb_buffer_pool_pages_free")) { 228 | Float pages_total = existing.get("status/innodb_buffer_pool_pages_total"); 229 | Float pages_data = existing.get("status/innodb_buffer_pool_pages_data"); 230 | Float pages_misc = existing.get("status/innodb_buffer_pool_pages_misc"); 231 | Float pages_dirty = existing.get("status/innodb_buffer_pool_pages_dirty"); 232 | Float pages_free = existing.get("status/innodb_buffer_pool_pages_free"); 233 | 234 | derived.put("newrelic/innodb_buffer_pool_pages_clean", pages_data - pages_dirty); 235 | derived.put("newrelic/innodb_buffer_pool_pages_dirty", pages_dirty); 236 | derived.put("newrelic/innodb_buffer_pool_pages_misc", pages_misc); 237 | derived.put("newrelic/innodb_buffer_pool_pages_free", pages_free); 238 | derived.put("newrelic/innodb_buffer_pool_pages_unassigned", pages_total - pages_data - pages_free - pages_misc); 239 | } 240 | 241 | /* Query Cache */ 242 | if (areRequiredMetricsPresent("Query Cache", existing, "status/qcache_hits", "status/com_select", "status/qcache_free_blocks", 243 | "status/qcache_total_blocks", "status/qcache_inserts", "status/qcache_not_cached")) { 244 | Float qc_hits = existing.get("status/qcache_hits"); 245 | Float reads = existing.get("status/com_select"); 246 | Float free = existing.get("status/qcache_free_blocks"); 247 | Float total = existing.get("status/qcache_total_blocks"); 248 | 249 | derived.put("newrelic/query_cache_hits", qc_hits); 250 | derived.put("newrelic/query_cache_misses", existing.get("status/qcache_inserts")); 251 | derived.put("newrelic/query_cache_not_cached", existing.get("status/qcache_not_cached")); 252 | 253 | Float pct_query_cache_hit_utilization = 0.0f; 254 | if (qc_hits + reads > 0) { 255 | pct_query_cache_hit_utilization = (qc_hits / (qc_hits + reads)) * 100.0f; 256 | } 257 | 258 | derived.put("newrelic/pct_query_cache_hit_utilization", pct_query_cache_hit_utilization); 259 | 260 | Float pct_query_cache_memory_in_use = 0.0f; 261 | if (total > 0) { 262 | pct_query_cache_memory_in_use = 100.0f - ((free / total) * 100.0f); 263 | } 264 | derived.put("newrelic/pct_query_cache_memory_in_use", pct_query_cache_memory_in_use); 265 | } 266 | 267 | /* Temp Table */ 268 | if (areRequiredMetricsPresent("Temp Tables", existing, "status/created_tmp_tables", "status/created_tmp_disk_tables")) { 269 | Float tmp_tables = existing.get("status/created_tmp_tables"); 270 | Float tmp_tables_disk = existing.get("status/created_tmp_disk_tables"); 271 | 272 | Float pct_tmp_tables_written_to_disk = 0.0f; 273 | if (tmp_tables > 0) { 274 | pct_tmp_tables_written_to_disk = (tmp_tables_disk / tmp_tables) * 100.0f; 275 | } 276 | derived.put("newrelic/pct_tmp_tables_written_to_disk", pct_tmp_tables_written_to_disk); 277 | } 278 | 279 | /* Replication specifics */ 280 | // "slave" category is a pre-requisite for these metrics 281 | if (isReportingForCategory("slave")) { 282 | if (areRequiredMetricsPresent("newrelic/replication_lag", existing, "slave/seconds_behind_master")) { 283 | derived.put("newrelic/replication_lag", existing.get("slave/seconds_behind_master")); 284 | } 285 | 286 | if (areRequiredMetricsPresent("newrelic/replication_status", existing, "slave/slave_io_running", "slave/slave_sql_running")) { 287 | int slave_io_thread_running = existing.get("slave/slave_io_running").intValue(); 288 | int slave_sql_thread_running = existing.get("slave/slave_sql_running").intValue(); 289 | 290 | /* both need to be YES, which is 1 */ 291 | Float replication_status = 1.0f; // Default as in ERROR 292 | if (slave_io_thread_running + slave_sql_thread_running == 2) { 293 | replication_status = 0.0f; 294 | } 295 | 296 | derived.put("newrelic/replication_status", replication_status); 297 | } 298 | 299 | if (areRequiredMetricsPresent("newrelic/slave_relay_log_bytes", existing, "slave/relay_log_pos")) { 300 | derived.put("newrelic/slave_relay_log_bytes", existing.get("slave/relay_log_pos")); 301 | } 302 | 303 | if (areRequiredMetricsPresent("newrelic/master_log_lag_bytes", existing, "slave/read_master_log_pos", "slave/exec_master_log_pos")) { 304 | derived.put("newrelic/master_log_lag_bytes", existing.get("slave/read_master_log_pos") 305 | - existing.get("slave/exec_master_log_pos")); 306 | } 307 | } else {// This is a hack because the NR UI can't handle it missing for graphs 308 | derived.put("newrelic/replication_lag", 0.0f); 309 | derived.put("newrelic/replication_status", 0.0f); 310 | derived.put("newrelic/slave_relay_log_bytes", 0.0f); 311 | derived.put("newrelic/master_log_lag_bytes", 0.0f); 312 | } 313 | 314 | return derived; 315 | } 316 | 317 | /** 318 | * This method does the reporting of metrics to New Relic 319 | * 320 | * @param Map results 321 | */ 322 | public void reportMetrics(Map results) { 323 | int count = 0; 324 | logger.debug("Collected ", results.size(), " MySQL metrics. ", getAgentInfo()); 325 | logger.debug(results); 326 | 327 | Iterator iter = results.keySet().iterator(); 328 | while (iter.hasNext()) { // Iterate over current metrics 329 | String key = iter.next().toLowerCase(); 330 | Float val = results.get(key); 331 | MetricMeta md = getMetricMeta(key); 332 | if (md != null) { // Metric Meta data exists (from metric.category.json) 333 | logger.debug(METRIC_LOG_PREFIX, key, SPACE, md, EQUALS, val); 334 | count++; 335 | 336 | if (md.isCounter()) { // Metric is a counter 337 | reportMetric(key, md.getUnit(), md.getCounter().process(val)); 338 | } else { // Metric is a fixed Number 339 | reportMetric(key, md.getUnit(), val); 340 | } 341 | } else { // md != null 342 | if (firstReport) { // Provide some feedback of available metrics for future reporting 343 | logger.debug("Not reporting identified metric ", key); 344 | } 345 | } 346 | } 347 | logger.debug("Reported to New Relic ", count, " metrics. ", getAgentInfo()); 348 | } 349 | 350 | /** 351 | * Is this agent reporting metrics for a specific category 352 | * 353 | * @param metricCategory 354 | * @return boolean 355 | */ 356 | boolean isReportingForCategory(String metricCategory) { 357 | return metrics.contains(metricCategory); 358 | } 359 | 360 | private String getAgentInfo() { 361 | if (agentInfo == null) { 362 | agentInfo = new StringBuilder().append("Agent Name: ").append(name).append(". Agent Version: ").append(version).toString(); 363 | } 364 | return agentInfo; 365 | } 366 | 367 | /** 368 | * This method creates the metric meta data that is derived from the provided configuration and New Relic specific metrics. 369 | */ 370 | private void createMetaData() { 371 | 372 | Map categories = getMetricCategories(); // Get current Metric Categories 373 | Iterator iter = categories.keySet().iterator(); 374 | while (iter.hasNext()) { 375 | String category = iter.next(); 376 | @SuppressWarnings("unchecked") 377 | Map attributes = (Map) categories.get(category); 378 | String valueMetrics = attributes.get("value_metrics"); 379 | if (valueMetrics != null) { 380 | Set metrics = new HashSet(Arrays.asList(valueMetrics.toLowerCase().replaceAll(SPACE, EMPTY_STRING).split(COMMA))); 381 | for (String s : metrics) { 382 | addMetricMeta(category + SEPARATOR + s, new MetricMeta(false)); 383 | } 384 | 385 | } 386 | String counterMetrics = attributes.get("counter_metrics"); 387 | if (counterMetrics != null) { 388 | Set metrics = new HashSet(Arrays.asList(counterMetrics.toLowerCase().replaceAll(SPACE, EMPTY_STRING).split(COMMA))); 389 | for (String s : metrics) { 390 | addMetricMeta(category + SEPARATOR + s, new MetricMeta(true)); 391 | } 392 | } 393 | } 394 | 395 | /* Define New Relic specific metrics used for default dashboards */ 396 | addMetricMeta("newrelic/volume_reads", new MetricMeta(true, "Queries/Second")); 397 | addMetricMeta("newrelic/volume_writes", new MetricMeta(true, "Queries/Second")); 398 | 399 | addMetricMeta("newrelic/bytes_reads", new MetricMeta(true, "Bytes/Second")); 400 | addMetricMeta("newrelic/bytes_writes", new MetricMeta(true, "Bytes/Second")); 401 | 402 | addMetricMeta("newrelic/connections_connected", new MetricMeta(false, "Connections")); 403 | addMetricMeta("newrelic/connections_running", new MetricMeta(false, "Connections")); 404 | addMetricMeta("newrelic/connections_cached", new MetricMeta(false, "Connections")); 405 | 406 | addMetricMeta("newrelic/innodb_bp_pages_created", new MetricMeta(true, "Pages/Second")); 407 | addMetricMeta("newrelic/innodb_bp_pages_read", new MetricMeta(true, "Pages/Second")); 408 | addMetricMeta("newrelic/innodb_bp_pages_written", new MetricMeta(true, "Pages/Second")); 409 | 410 | addMetricMeta("newrelic/query_cache_hits", new MetricMeta(true, "Queries/Seconds")); 411 | addMetricMeta("newrelic/query_cache_misses", new MetricMeta(true, "Queries/Seconds")); 412 | addMetricMeta("newrelic/query_cache_not_cached", new MetricMeta(true, "Queries/Seconds")); 413 | 414 | addMetricMeta("newrelic/replication_lag", new MetricMeta(false, "Seconds")); 415 | addMetricMeta("newrelic/replication_status", new MetricMeta(false, "State")); 416 | 417 | addMetricMeta("newrelic/pct_connection_utilization", new MetricMeta(false, "Percent")); 418 | addMetricMeta("newrelic/pct_innodb_buffer_pool_hit_ratio", new MetricMeta(false, "Percent")); 419 | addMetricMeta("newrelic/pct_query_cache_hit_utilization", new MetricMeta(false, "Percent")); 420 | addMetricMeta("newrelic/pct_query_cache_memory_in_use", new MetricMeta(false, "Percent")); 421 | addMetricMeta("newrelic/pct_tmp_tables_written_to_disk", new MetricMeta(false, "Percent")); 422 | 423 | addMetricMeta("newrelic/innodb_fsyncs_data", new MetricMeta(true, "Fsyncs/Second")); 424 | addMetricMeta("newrelic/innodb_fsyncs_os_log", new MetricMeta(true, "Fsyncs/Second")); 425 | 426 | addMetricMeta("newrelic/slave_relay_log_bytes", new MetricMeta(true, "Bytes/Second")); 427 | addMetricMeta("newrelic/master_log_lag_bytes", new MetricMeta(true, "Bytes/Second")); 428 | 429 | /* Define improved metric values for certain general metrics */ 430 | addMetricMeta("status/aborted_clients", new MetricMeta(true, "Connections/Second")); 431 | addMetricMeta("status/aborted_connects", new MetricMeta(true, "Connections/Second")); 432 | 433 | addMetricMeta("status/bytes_sent", new MetricMeta(true, "Bytes/Second")); 434 | addMetricMeta("status/bytes_received", new MetricMeta(true, "Bytes/Second")); 435 | 436 | addMetricMeta("status/com_select", new MetricMeta(true, "Selects/Second")); 437 | addMetricMeta("status/com_insert", new MetricMeta(true, "Inserts/Second")); 438 | addMetricMeta("status/com_insert_select", new MetricMeta(true, "Inserts/Second")); 439 | addMetricMeta("status/com_update", new MetricMeta(true, "Updates/Second")); 440 | addMetricMeta("status/com_update_multi", new MetricMeta(true, "Updates/Second")); 441 | addMetricMeta("status/com_delete", new MetricMeta(true, "Deletes/Second")); 442 | addMetricMeta("status/com_delete_multi", new MetricMeta(true, "Deletes/Second")); 443 | addMetricMeta("status/com_replace", new MetricMeta(true, "Replaces/Second")); 444 | addMetricMeta("status/com_replace_select", new MetricMeta(true, "Replaces/Second")); 445 | 446 | addMetricMeta("status/slow_queries", new MetricMeta(true, "Queries/Second")); 447 | addMetricMeta("status/created_tmp_tables", new MetricMeta(true, "Queries/Second")); 448 | addMetricMeta("status/created_tmp_disk_tables", new MetricMeta(true, "Queries/Second")); 449 | 450 | addMetricMeta("status/innodb_buffer_pool_pages_flushed", new MetricMeta(true, "Pages/Second")); 451 | 452 | addMetricMeta("newrelic/innodb_buffer_pool_pages_clean", new MetricMeta(false, "Pages")); 453 | addMetricMeta("newrelic/innodb_buffer_pool_pages_dirty", new MetricMeta(false, "Pages")); 454 | addMetricMeta("newrelic/innodb_buffer_pool_pages_misc", new MetricMeta(false, "Pages")); 455 | addMetricMeta("newrelic/innodb_buffer_pool_pages_free", new MetricMeta(false, "Pages")); 456 | addMetricMeta("newrelic/innodb_buffer_pool_pages_unassigned", new MetricMeta(false, "Pages")); 457 | 458 | addMetricMeta("status/innodb_data_fsyncs", new MetricMeta(true, "Fsyncs/Second")); 459 | addMetricMeta("status/innodb_os_log_fsyncs", new MetricMeta(true, "Fsyncs/Second")); 460 | 461 | addMetricMeta("status/innodb_os_log_written", new MetricMeta(true, "Bytes/Second")); 462 | 463 | /* Query Cache Units */ 464 | addMetricMeta("status/qcache_free_blocks", new MetricMeta(false, "Blocks")); 465 | addMetricMeta("status/qcache_free_memory", new MetricMeta(false, "Bytes")); 466 | addMetricMeta("status/qcache_hits", new MetricMeta(true, "Queries/Second")); 467 | addMetricMeta("status/qcache_inserts", new MetricMeta(true, "Queries/Second")); 468 | addMetricMeta("status/qcache_lowmem_prunes", new MetricMeta(true, "Queries/Second")); 469 | addMetricMeta("status/qcache_not_cached", new MetricMeta(true, "Queries/Second")); 470 | addMetricMeta("status/qcache_queries_in_cache", new MetricMeta(false, "Queries")); 471 | addMetricMeta("status/qcache_total_blocks", new MetricMeta(false, "Blocks")); 472 | 473 | addMetricMeta("innodb_status/history_list_length", new MetricMeta(false, "Pages")); 474 | addMetricMeta("innodb_status/queries_inside_innodb", new MetricMeta(false, "Queries")); 475 | addMetricMeta("innodb_status/queries_in_queue", new MetricMeta(false, "Queries")); 476 | addMetricMeta("innodb_status/checkpoint_age", new MetricMeta(false, "Bytes")); 477 | 478 | addMetricMeta("master/position", new MetricMeta(true, "Bytes/Second")); 479 | addMetricMeta("slave/relay_log_pos", new MetricMeta(true, "Bytes/Second")); 480 | } 481 | 482 | /** 483 | * Add the given metric meta information to the Map of all metric meta information for this agent 484 | * 485 | * @param String key 486 | * @param Metric mm 487 | */ 488 | private void addMetricMeta(String key, MetricMeta mm) { 489 | metricsMeta.put(key.toLowerCase(), mm); 490 | } 491 | 492 | /** 493 | * This provides a lazy instantiation of a MySQL metric where no meta data was defined and means new metrics can be captured automatically. 494 | * 495 | * A default metric is a integer value 496 | * 497 | * @param String Metric to look up 498 | * @return MetridMeta Structure of information about the metric 499 | */ 500 | private MetricMeta getMetricMeta(String key) { 501 | if (key.startsWith(INNODB_MUTEX_CATEGORY) && !metricsMeta.containsKey(key)) { // This is a catch all for dynamic name metrics 502 | addMetricMeta(key, new MetricMeta(true, "Operations/Second")); 503 | } 504 | 505 | return metricsMeta.get(key.toLowerCase()); // Look for existing meta data on metric 506 | } 507 | 508 | /** 509 | * Private utility function to validate that all required data is present for constructing atomic metrics 510 | * 511 | * @param category - a display name for which metric category will not be included if a given key is not present 512 | * @param map - the map of available data points 513 | * @param keys - keys that are expected to be present for this operation 514 | * @return true if all expected keys are present, otherwise false 515 | */ 516 | private boolean areRequiredMetricsPresent(String category, Map map, String... keys) { 517 | for (String key : keys) { 518 | if (!map.containsKey(key)) { 519 | if (firstReport) { // Only report missing category data on the first run so as not to clutter the log 520 | logger.debug("Not reporting on '", category, "' due to missing data field '", key, "'"); 521 | } 522 | 523 | return false; 524 | } 525 | } 526 | 527 | return true; 528 | } 529 | 530 | /** 531 | * Return the human readable name for this agent. 532 | * 533 | * @return String 534 | */ 535 | @Override 536 | public String getComponentHumanLabel() { 537 | return name; 538 | } 539 | 540 | /** 541 | * Return the map of metric categories 542 | * 543 | * @return Map 544 | */ 545 | public Map getMetricCategories() { 546 | return metricCategories; 547 | } 548 | } 549 | -------------------------------------------------------------------------------- /src/com/newrelic/plugins/mysql/instance/MySQLAgentFactory.java: -------------------------------------------------------------------------------- 1 | package com.newrelic.plugins.mysql.instance; 2 | 3 | import static com.newrelic.plugins.mysql.util.Constants.COMMA; 4 | import static com.newrelic.plugins.mysql.util.Constants.EMPTY_STRING; 5 | 6 | import java.util.Arrays; 7 | import java.util.HashMap; 8 | import java.util.HashSet; 9 | import java.util.Map; 10 | import java.util.Set; 11 | 12 | import org.json.simple.JSONArray; 13 | import org.json.simple.JSONObject; 14 | 15 | import com.newrelic.metrics.publish.Agent; 16 | import com.newrelic.metrics.publish.AgentFactory; 17 | import com.newrelic.metrics.publish.configuration.ConfigurationException; 18 | 19 | /** 20 | * This class produces the necessary Agents to perform gathering and reporting 21 | * metrics for the MySQL plugin 22 | * 23 | * @author Ronald Bradford me@ronaldbradford.com 24 | * 25 | */ 26 | public class MySQLAgentFactory extends AgentFactory { 27 | 28 | private static final String CATEGORY_CONFIG_FILE = "metric.category.json"; 29 | 30 | /** 31 | * Configure an agent based on an entry in the properties file. There may be 32 | * multiple agents per plugin 33 | */ 34 | @Override 35 | public Agent createConfiguredAgent(Map properties) throws ConfigurationException { 36 | String name = (String) properties.get("name"); 37 | String host = (String) properties.get("host"); 38 | String user = (String) properties.get("user"); 39 | String passwd = (String) properties.get("passwd"); 40 | String conn_properties = (String) properties.get("properties"); 41 | String metrics = (String) properties.get("metrics"); 42 | 43 | if (name == null || EMPTY_STRING.equals(name)) { 44 | throw new ConfigurationException("The 'name' attribute is required. Have you configured the 'config/plugin.json' file?"); 45 | } 46 | 47 | /** 48 | * Use pre-defined defaults to simplify configuration 49 | */ 50 | if (host == null || EMPTY_STRING.equals(host)) { 51 | host = MySQLAgent.AGENT_DEFAULT_HOST; 52 | } 53 | if (user == null || EMPTY_STRING.equals(user)) { 54 | user = MySQLAgent.AGENT_DEFAULT_USER; 55 | } 56 | if (passwd == null) { 57 | passwd = MySQLAgent.AGENT_DEFAULT_PASSWD; 58 | } 59 | if (conn_properties == null || EMPTY_STRING.equals(conn_properties)) { 60 | conn_properties = MySQLAgent.AGENT_DEFAULT_PROPERTIES; 61 | } 62 | if (metrics == null || EMPTY_STRING.equals(metrics)) { 63 | metrics = MySQLAgent.AGENT_DEFAULT_METRICS; 64 | } 65 | 66 | return new MySQLAgent(name, host, user, passwd, conn_properties, 67 | processMetricCategories(metrics), readCategoryConfiguration()); 68 | } 69 | 70 | /** 71 | * Read metric category information that enables the dynamic definition of 72 | * MySQL metrics that can be collected. 73 | * 74 | * @return Map Categories and the meta data about the categories 75 | * @throws ConfigurationException 76 | */ 77 | public Map readCategoryConfiguration() throws ConfigurationException { 78 | Map metricCategories = new HashMap(); 79 | try { 80 | JSONArray json = readJSONFile(CATEGORY_CONFIG_FILE); 81 | for (int i = 0; i < json.size(); i++) { 82 | JSONObject obj = (JSONObject) json.get(i); 83 | String category = (String) obj.get("category"); 84 | metricCategories.put(category, obj); 85 | } 86 | } catch (ConfigurationException e) { 87 | throw new ConfigurationException("'metric_categories' could not be found in the 'plugin.json' configuration file"); 88 | } 89 | return metricCategories; 90 | } 91 | 92 | Set processMetricCategories(String metrics) { 93 | String[] categories = metrics.toLowerCase().split(COMMA); 94 | Set set = new HashSet(Arrays.asList(categories)); 95 | set.remove(EMPTY_STRING); // in case of trailing comma or two consecutive commas 96 | return set; 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/com/newrelic/plugins/mysql/util/Constants.java: -------------------------------------------------------------------------------- 1 | package com.newrelic.plugins.mysql.util; 2 | 3 | import java.util.regex.Pattern; 4 | 5 | public class Constants { 6 | 7 | public static final String COMMA = ","; 8 | public static final String SLASH = "/"; 9 | public static final String SPACE = " "; 10 | public static final String EMPTY_STRING = ""; 11 | public static final String UNDERSCORE = "_"; 12 | public static final String LEFT_PAREN = "("; 13 | public static final String RIGHT_PAREN = ")"; 14 | public static final String ARROW = "->"; 15 | public static final String EQUALS = "="; 16 | public static final String NEW_LINE = "\n"; 17 | public static final String SQL = "SQL"; 18 | public static final String RESULT = "result"; 19 | public static final String COUNTER = "[counter]"; 20 | public static final String METRIC_LOG_PREFIX = "Metric "; 21 | 22 | public static final String SEPARATOR = "/"; 23 | public static final String PING = "/* ping */ SELECT 1"; 24 | public static final Pattern VALID_METRIC_PATTERN = Pattern.compile("(-)?(\\.)?\\d+(\\.\\d+)?"); // Only integers and floats are valid metric values 25 | public static final Pattern SPACE_PATTERN = Pattern.compile(" "); 26 | 27 | public static final String JDBC_URL = "jdbc:mysql://"; 28 | public static final String PASSWORD_FILTERED = "/PASSWORD_FILTERED"; 29 | 30 | public static final String ROW = "row"; 31 | public static final String SET = "set"; 32 | public static final String SPECIAL = "special"; 33 | 34 | public static final String ON = "ON"; 35 | public static final String OFF = "OFF"; 36 | public static final String TRUE = "TRUE"; 37 | public static final String NONE = "NONE"; 38 | public static final String YES = "YES"; 39 | public static final String NO = "NO"; 40 | public static final String NULL = "NULL"; 41 | 42 | public static final String ONE = "1"; 43 | public static final String NEG_ONE = "-1"; 44 | public static final String ZERO = "0"; 45 | 46 | public static final String NEW_RELIC_CATEGORY = "newrelic"; 47 | public static final String STATUS_CATEGORY = "status"; 48 | 49 | public static final String INNODB_MUTEX_REGEX = "[&\\[\\]]"; 50 | public static final String INNODB_MUTEX_CATEGORY = "innodb_mutex/"; 51 | 52 | public static final String SHOW_ENGINE_INNODB_MUTEX = "SHOW ENGINE INNODB MUTEX"; 53 | public static final String SHOW_ENGINE_INNODB_STATUS = "SHOW ENGINE INNODB STATUS"; 54 | 55 | public static final String HISTORY_LIST_LENGTH = "History list length"; 56 | public static final String LOG_SEQUENCE_NUMBER = "Log sequence number"; 57 | public static final String LAST_CHECKPOINT_AT = "Last checkpoint at "; 58 | public static final Pattern QUERIES_INSIDE_INNODB_REGEX_PATTERN = Pattern.compile(".* queries inside InnoDB.*"); 59 | public static final String QUERIES_INSIDE_INNODB_REGEX2 = " queries inside InnoDB.*"; 60 | public static final String QUERIES_IN_QUEUE_REGEX = ".* queries inside InnoDB, "; 61 | public static final String QUERIES_IN_QUEUE_REGEX2 = " queries in queue"; 62 | 63 | public static final String SECONDS_BEHIND_MASTER = "seconds_behind_master"; 64 | public static final String HISTORY_LIST_LENGTH_METRIC = "history_list_length"; 65 | public static final String LOG_SEQUENCE_NUMBER_METRIC = "log_sequence_number"; 66 | public static final String LAST_CHECKPOINT_METRIC = "last_checkpoint"; 67 | public static final String QUERIES_INSIDE_INNODB_METRIC = "queries_inside_innodb"; 68 | public static final String QUERIES_IN_QUEUE = "queries_in_queue"; 69 | public static final String CHECKPOINT_AGE_METRIC = "checkpoint_age"; 70 | } 71 | -------------------------------------------------------------------------------- /test/com/newrelic/plugins/mysql/TestMetricMeta.java: -------------------------------------------------------------------------------- 1 | package com.newrelic.plugins.mysql; 2 | 3 | import static org.junit.Assert.*; 4 | 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | 8 | /** 9 | * This class performs test cases on the MySQL class All enabled tests pass on version 5.5 of MySQL. 10 | * 11 | * @author Ronald Bradford me@ronaldbradford.com 12 | * 13 | */ 14 | 15 | public class TestMetricMeta { 16 | 17 | @Before 18 | public void setUp() { 19 | } 20 | 21 | @Test 22 | public void verifyMetricValue() { 23 | MetricMeta mm = new MetricMeta(false); 24 | assertNotNull(mm); 25 | assertNull(mm.getCounter()); 26 | assertEquals(MetricMeta.DEFAULT_UNIT, mm.getUnit()); 27 | } 28 | 29 | @Test 30 | public void verifyMetricCounter() { 31 | MetricMeta mm = new MetricMeta(true); 32 | assertNotNull(mm); 33 | assertNotNull(mm.getCounter()); 34 | assertEquals(MetricMeta.DEFAULT_COUNTER_UNIT, mm.getUnit()); 35 | } 36 | 37 | @Test 38 | public void verifyMetricCounterWithSpecificUnit() { 39 | String unit = "ops/sec"; 40 | MetricMeta mm = new MetricMeta(true, unit); 41 | assertNotNull(mm); 42 | assertEquals(unit, mm.getUnit()); 43 | } 44 | 45 | @Test 46 | public void testOperator() { 47 | boolean isCounter = false; 48 | assertEquals(MetricMeta.DEFAULT_UNIT, isCounter ? MetricMeta.DEFAULT_COUNTER_UNIT : MetricMeta.DEFAULT_UNIT); 49 | isCounter = true; 50 | assertEquals(MetricMeta.DEFAULT_COUNTER_UNIT, isCounter ? MetricMeta.DEFAULT_COUNTER_UNIT : MetricMeta.DEFAULT_UNIT); 51 | } 52 | } -------------------------------------------------------------------------------- /test/com/newrelic/plugins/mysql/TestMySQL.java: -------------------------------------------------------------------------------- 1 | package com.newrelic.plugins.mysql; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertFalse; 5 | import static org.junit.Assert.assertNotNull; 6 | import static org.junit.Assert.assertNull; 7 | import static org.junit.Assert.assertTrue; 8 | 9 | import java.sql.Connection; 10 | import java.sql.SQLException; 11 | import java.sql.Statement; 12 | import java.util.Map; 13 | 14 | import org.junit.Before; 15 | import org.junit.Ignore; 16 | import org.junit.Test; 17 | 18 | import com.newrelic.plugins.mysql.instance.MySQLAgent; 19 | 20 | /** 21 | * This class performs test cases on the MySQL class. All enabled tests pass on version 5.5 of MySQL. 22 | * 23 | * @author Ronald Bradford me@ronaldbradford.com 24 | * 25 | */ 26 | 27 | public class TestMySQL { 28 | 29 | @Before 30 | public void setUp() { 31 | } 32 | 33 | @Test 34 | public void verifyValidConnection() { 35 | Connection c = new MySQL().getConnection(MySQLAgent.AGENT_DEFAULT_HOST, MySQLAgent.AGENT_DEFAULT_USER, MySQLAgent.AGENT_DEFAULT_PASSWD, 36 | MySQLAgent.AGENT_DEFAULT_PROPERTIES); 37 | assertNotNull(c); 38 | } 39 | 40 | @Test 41 | public void verifyInvalidConnectionWithBadHost() { 42 | Connection c = new MySQL().getConnection("bad_host", MySQLAgent.AGENT_DEFAULT_USER, MySQLAgent.AGENT_DEFAULT_PASSWD, 43 | MySQLAgent.AGENT_DEFAULT_PROPERTIES); 44 | assertNull(c); 45 | } 46 | 47 | @Test 48 | public void verifyInvalidConnectionWithBadUser() { 49 | Connection c = new MySQL().getConnection(MySQLAgent.AGENT_DEFAULT_HOST, "bad_user", MySQLAgent.AGENT_DEFAULT_PASSWD, 50 | MySQLAgent.AGENT_DEFAULT_PROPERTIES); 51 | assertNull(c); 52 | } 53 | 54 | @Test 55 | public void verifyInvalidConnectionWithBadPassword() { 56 | Connection c = new MySQL().getConnection(MySQLAgent.AGENT_DEFAULT_HOST, MySQLAgent.AGENT_DEFAULT_USER, "bad_password", 57 | MySQLAgent.AGENT_DEFAULT_PROPERTIES); 58 | assertNull(c); 59 | } 60 | 61 | @Test 62 | public void verifyInvalidConnectionWithBadProperties() { 63 | Connection c = new MySQL().getConnection(MySQLAgent.AGENT_DEFAULT_HOST, MySQLAgent.AGENT_DEFAULT_USER, MySQLAgent.AGENT_DEFAULT_PASSWD, 64 | "bad_properties"); 65 | assertNull(c); 66 | } 67 | 68 | @Test 69 | public void verifyTransformedStringMetrics() { 70 | assertEquals("FRED", MySQL.transformStringMetric("FRED")); 71 | assertEquals("1", MySQL.transformStringMetric("ON")); 72 | assertEquals("1", MySQL.transformStringMetric("TRUE")); 73 | assertEquals("0", MySQL.transformStringMetric("OFF")); 74 | assertEquals("0", MySQL.transformStringMetric("NONE")); 75 | assertEquals("-1", MySQL.transformStringMetric("NULL")); 76 | assertEquals("FALSE", MySQL.transformStringMetric("FALSE")); 77 | } 78 | 79 | @Test 80 | public void verifyValidMetricValues() { 81 | assertTrue(MySQL.validMetricValue("0")); 82 | assertTrue(MySQL.validMetricValue("1")); 83 | assertTrue(MySQL.validMetricValue("-1")); 84 | assertTrue(MySQL.validMetricValue("100")); 85 | assertTrue(MySQL.validMetricValue("-100")); 86 | assertTrue(MySQL.validMetricValue("456.7789")); 87 | assertTrue(MySQL.validMetricValue("-456.7789")); 88 | assertTrue(MySQL.validMetricValue(".234")); 89 | assertTrue(MySQL.validMetricValue("0.234")); 90 | assertTrue(MySQL.validMetricValue("-.234")); 91 | assertTrue(MySQL.validMetricValue("-0.234")); 92 | } 93 | 94 | @Test 95 | public void verifyInValidMetricValues() { 96 | assertFalse(MySQL.validMetricValue("")); 97 | assertFalse(MySQL.validMetricValue("5.25.45a")); 98 | assertFalse(MySQL.validMetricValue("10.38.78.86")); 99 | assertFalse(MySQL.validMetricValue("+123")); 100 | assertFalse(MySQL.validMetricValue("123-34")); 101 | assertFalse(MySQL.validMetricValue("123.")); 102 | assertFalse(MySQL.validMetricValue("10a")); 103 | assertFalse(MySQL.validMetricValue("abc")); 104 | } 105 | 106 | @Test 107 | public void verifyValidMetricValuesThatGetTranformed() { 108 | assertTrue(MySQL.validMetricValue(MySQL.transformStringMetric("ON"))); 109 | assertTrue(MySQL.validMetricValue(MySQL.transformStringMetric("TRUE"))); 110 | assertTrue(MySQL.validMetricValue(MySQL.transformStringMetric("OFF"))); 111 | assertTrue(MySQL.validMetricValue(MySQL.transformStringMetric("NONE"))); 112 | assertTrue(MySQL.validMetricValue(MySQL.transformStringMetric("NULL"))); 113 | } 114 | 115 | @Test 116 | public void runSQLSingleStatusValid() { 117 | Connection c = new MySQL().getConnection(MySQLAgent.AGENT_DEFAULT_HOST, MySQLAgent.AGENT_DEFAULT_USER, MySQLAgent.AGENT_DEFAULT_PASSWD, 118 | MySQLAgent.AGENT_DEFAULT_PROPERTIES); 119 | assertNotNull(c); 120 | Map results = MySQL.runSQL(c, "status", "SHOW GLOBAL STATUS LIKE 'Com_xa_rollback'", "set"); 121 | assertEquals(1, results.size()); 122 | assertEquals(0, results.get("status/com_xa_rollback").intValue()); // A status likely to never have a value 123 | } 124 | 125 | @Test 126 | public void runSQLSingleStatusValue() { 127 | Connection c = new MySQL().getConnection(MySQLAgent.AGENT_DEFAULT_HOST, MySQLAgent.AGENT_DEFAULT_USER, MySQLAgent.AGENT_DEFAULT_PASSWD, 128 | MySQLAgent.AGENT_DEFAULT_PROPERTIES); 129 | assertNotNull(c); 130 | Map results = MySQL.runSQL(c, "status", "SHOW GLOBAL STATUS LIKE 'Uptime'", "set"); 131 | assertEquals(1, results.size()); 132 | assertTrue(results.get("status/uptime").intValue() > 0); // A status that will always be > 0 133 | } 134 | 135 | @Ignore 136 | @Test 137 | public void runSQLSingleStatusInvalid() { 138 | Connection c = new MySQL().getConnection(MySQLAgent.AGENT_DEFAULT_HOST, MySQLAgent.AGENT_DEFAULT_USER, MySQLAgent.AGENT_DEFAULT_PASSWD, 139 | MySQLAgent.AGENT_DEFAULT_PROPERTIES); 140 | assertNotNull(c); 141 | Map results = MySQL.runSQL(c, "status", "SHOW GLOBAL VARIABLES LIKE 'version'", "set"); 142 | assertEquals(0, results.size()); // This is removed because value is a string 143 | } 144 | 145 | @Test 146 | public void runSQLSingleStatusTranslated() { 147 | Connection c = new MySQL().getConnection(MySQLAgent.AGENT_DEFAULT_HOST, MySQLAgent.AGENT_DEFAULT_USER, MySQLAgent.AGENT_DEFAULT_PASSWD, 148 | MySQLAgent.AGENT_DEFAULT_PROPERTIES); 149 | assertNotNull(c); 150 | Map results = MySQL.runSQL(c, "status", "SHOW GLOBAL STATUS LIKE 'Compression'", "set"); 151 | assertEquals(1, results.size()); 152 | assertEquals(0, results.get("status/compression").intValue()); //Translated from OFF 153 | } 154 | 155 | public void closeConnection(Connection c) { 156 | try { 157 | if (c != null) { 158 | c.close(); 159 | } 160 | } catch (SQLException e) { 161 | } 162 | } 163 | 164 | @Test 165 | public void runTranslateStringToNumber() { 166 | assertEquals(5, MySQL.translateStringToNumber("5").intValue()); 167 | assertEquals(java.lang.Float.class, MySQL.translateStringToNumber("5").getClass()); 168 | assertEquals(5.0f, MySQL.translateStringToNumber("5.0"), 0.0001f); 169 | assertEquals(java.lang.Float.class, MySQL.translateStringToNumber("5.0").getClass()); 170 | Float x = new Float("37107795968"); 171 | assertEquals(x, MySQL.translateStringToNumber("37107795968")); 172 | } 173 | 174 | @Test 175 | public void verifyFloat() { 176 | String val = "0.00"; 177 | assertTrue(val.matches("\\d*\\.\\d*")); 178 | } 179 | 180 | @Test 181 | public void verifyNotFloat() { 182 | String val = "70797"; 183 | assertFalse(val.matches("\\d*\\.\\d*")); 184 | } 185 | 186 | @Ignore 187 | @Test 188 | public void runSHOWENGINEINNODBSTATUS() throws SQLException { 189 | Connection c = new MySQL().getConnection(MySQLAgent.AGENT_DEFAULT_HOST, MySQLAgent.AGENT_DEFAULT_USER, MySQLAgent.AGENT_DEFAULT_PASSWD, 190 | MySQLAgent.AGENT_DEFAULT_PROPERTIES); 191 | assertNotNull(c); 192 | Statement stmt = c.createStatement(); 193 | String SQL = "SHOW ENGINE INNODB STATUS"; 194 | Map results = MySQL.processInnoDBStatus(stmt.executeQuery(SQL), "innodb_status"); 195 | assertEquals(3, results.size()); 196 | assertNotNull(results.get("innodb_status/history_list_length")); 197 | 198 | String s = "0 queries inside innodb, 0 queries in queue"; 199 | assertTrue(s.matches(".*queries inside innodb.*")); 200 | 201 | s = "&dict->stats[i]"; 202 | assertEquals("dict_statsi", s.replaceAll("[&\\[\\]]", "").replaceAll("->", "_")); 203 | } 204 | 205 | @Test 206 | public void testProcessInnoDBStatus() { 207 | Map results = MySQL.processInnoDBStatus(INNODB_STATUS, "test"); 208 | 209 | assertEquals(1096f, results.get("test/history_list_length"), 0.0001f); 210 | assertEquals(11727772890f, results.get("test/log_sequence_number"), 0.0001f); 211 | assertEquals(11727772890f, results.get("test/last_checkpoint"), 0.0001f); 212 | assertEquals(0f, results.get("test/queries_inside_innodb"), 0.0001f); 213 | assertEquals(0f, results.get("test/queries_in_queue"), 0.0001f); 214 | assertEquals(0, results.get("test/checkpoint_age"), 0.0001f); 215 | } 216 | 217 | @Test 218 | public void testBuildString() { 219 | assertEquals("onetwo ,three", MySQL.buildString("one", "two", " ", ",", "three")); 220 | assertEquals(" ", MySQL.buildString("", " ")); 221 | } 222 | 223 | private static final String INNODB_STATUS = "=====================================\n" 224 | + "131118 19:46:19 INNODB MONITOR OUTPUT\n" 225 | + "=====================================\n" 226 | + "Per second averages calculated from the last 31 seconds\n" 227 | + "-----------------\n" 228 | + "BACKGROUND THREAD\n" 229 | + "-----------------\n" 230 | + "srv_master_thread loops: 105382 1_second, 105381 sleeps, 9446 10_second, 12076 background, 12075 flush\n" 231 | + "srv_master_thread log flush and writes: 105427\n" 232 | + "----------\n" 233 | + "SEMAPHORES\n" 234 | + "----------\n" 235 | + "OS WAIT ARRAY INFO: reservation count 238575, signal count 83540\n" 236 | + "Mutex spin waits 1890366, rounds 3580179, OS waits 54697\n" 237 | + "RW-shared spins 68558, rounds 3445412, OS waits 98364\n" 238 | + "RW-excl spins 349350, rounds 3954294, OS waits 69291\n" 239 | + "Spin rounds per wait: 1.89 mutex, 50.26 RW-shared, 11.32 RW-excl\n" 240 | + "------------------------\n" 241 | + "LATEST FOREIGN KEY ERROR\n" 242 | + "------------------------\n" 243 | + "131031 12:02:00 Transaction:\n" 244 | + "TRANSACTION 987CE1, ACTIVE 0 sec inserting\n" 245 | + "mysql tables in use 1, locked 1\n" 246 | + "3 lock struct(s), heap size 376, 1 row lock(s), undo log entries 1\n" 247 | + "MySQL thread id 703, OS thread handle 0x10e547000, query id 1170599 localhost 127.0.0.1 root update\n" 248 | + "INSERT IGNORE INTO table_infos (table_name, account_id, begin_time, end_time, shard_id, frequency) VALUES('ts_477_16009_1d',477,'2013-10-31 00:00:00','2013-11-01 00:00:00',2,60)\n" 249 | + "Foreign key constraint fails for table `test`.`table_infos`:\n" + ",\n" 250 | + " CONSTRAINT `table_infos_ibfk_1` FOREIGN KEY (`account_id`) REFERENCES `accounts` (`id`)\n" 251 | + "Trying to add in child table, in index `index_table_infos_on_account_id_and_begin_time` tuple:\n" + "DATA TUPLE: 3 fields;\n" 252 | + " 0: len 4; hex 800001dd; asc ;;\n" + " 1: len 8; hex 8000124f1ef3a3c0; asc O ;;\n" + " 2: len 4; hex 8000006a; asc j;;\n" + "\n" 253 | + "But in parent table `test`.`accounts`, in index `PRIMARY`,\n" + "the closest match we can find is record:\n" 254 | + "PHYSICAL RECORD: n_fields 64; compact format; info bits 32\n" + " 0: len 4; hex 80000361; asc a;;\n" 255 | + " 1: len 6; hex 0000009872ba; asc r ;;\n" + " 2: len 7; hex 49000006881cc2; asc I ;;\n" 256 | + " 3: len 19; hex 54657374204163636f756e7420383232323033; asc Test Account 822203;;\n" + " 4: SQL NULL;\n" 257 | + " 5: len 6; hex 616374697665; asc active;;\n" 258 | + " 6: len 30; hex 67656e6572617465645f746573773655f6b65795f383232; asc generated_test_key_822; (total 33 bytes);\n" + " 7: SQL NULL;\n" 259 | + " 8: len 4; hex 80000001; asc ;;\n" + " 9: len 1; hex 80; asc ;;\n" + " 10: SQL NULL;\n" + " 11: SQL NULL;\n" + " 12: SQL NULL;\n" 260 | + " 13: SQL NULL;\n" + " 14: SQL NULL;\n" + " 15: SQL NULL;\n" + " 16: SQL NULL;\n" + " 17: SQL NULL;\n" + " 18: len 1; hex 80; asc ;;\n" 261 | + " 19: SQL NULL;\n" + " 20: len 1; hex 80; asc ;;\n" + " 21: SQL NULL;\n" + " 22: SQL NULL;\n" + " 23: len 4; hex 80000002; asc ;;\n" 262 | + " 24: SQL NULL;\n" + " 25: SQL NULL;\n" + " 26: SQL NULL;\n" + " 27: len 11; hex 6372656417264; asc id;;\n" + " 28: SQL NULL;\n" 263 | + " 29: len 4; hex 80000001; asc ;;\n" + " 30: SQL NULL;\n" + " 31: SQL NULL;\n" + " 32: SQL NULL;\n" + " 33: len 1; hex 80; asc ;;\n" 264 | + " 34: SQL NULL;\n" + " 35: SQL NULL;\n" + " 36: SQL NULL;\n" + " 37: SQL NULL;\n" + " 38: SQL NULL;\n" + " 39: SQL NULL;\n" + " 40: SQL NULL;\n" 265 | + " 41: SQL NULL;\n" + " 42: len 4; hex 80000000; asc ;;\n" + " 43: SQL NULL;\n" + " 44: len 10; hex 6b65736313531; asc key_686151;;\n" 266 | + " 45: SQL NULL;\n" + " 46: len 4; hex 80000001; asc ;;\n" + " 47: SQL NULL;\n" + " 48: len 1; hex 81; asc ;;\n" + " 49: SQL NULL;\n" 267 | + " 50: SQL NULL;\n" + " 51: SQL NULL;\n" + " 52: SQL NULL;\n" + " 53: SQL NULL;\n" + " 54: SQL NULL;\n" + " 55: SQL NULL;\n" 268 | + " 56: len 1; hex 80; asc ;;\n" + " 57: SQL NULL;\n" + " 58: len 1; hex 81; asc ;;\n" + " 59: SQL NULL;\n" + " 60: SQL NULL;\n" 269 | + " 61: len 4; hex 80000001; asc ;;\n" + " 62: SQL NULL;\n" + " 63: len 4; hex 80000001; asc ;;\n" + "\n" + "------------\n" 270 | + "TRANSACTIONS\n" + "------------\n" + "Trx id counter A36FB5\n" + "Purge done for trx's n:o < A36FAF undo n:o < 0\n" 271 | + "History list length 1096\n" + "LIST OF TRANSACTIONS FOR EACH SESSION:\n" + "---TRANSACTION 0, not started\n" 272 | + "MySQL thread id 29121, OS thread handle 0x10bcb5000, query id 1934861 localhost root\n" + "SHOW ENGINE INNODB STATUS\n" + "--------\n" 273 | + "FILE I/O\n" + "--------\n" + "I/O thread 0 state: waiting for i/o request (insert buffer thread)\n" 274 | + "I/O thread 1 state: waiting for i/o request (log thread)\n" + "I/O thread 2 state: waiting for i/o request (read thread)\n" 275 | + "I/O thread 3 state: waiting for i/o request (read thread)\n" + "I/O thread 4 state: waiting for i/o request (read thread)\n" 276 | + "I/O thread 5 state: waiting for i/o request (read thread)\n" + "I/O thread 6 state: waiting for i/o request (write thread)\n" 277 | + "I/O thread 7 state: waiting for i/o request (write thread)\n" + "I/O thread 8 state: waiting for i/o request (write thread)\n" 278 | + "I/O thread 9 state: waiting for i/o request (write thread)\n" + "Pending normal aio reads: 0 [0, 0, 0, 0] , aio writes: 0 [0, 0, 0, 0] ,\n" 279 | + " ibuf aio reads: 0, log i/o's: 0, sync i/o's: 0\n" + "Pending flushes (fsync) log: 0; buffer pool: 0\n" 280 | + "4731 OS file reads, 1354071 OS file writes, 941695 OS fsyncs\n" + "0.00 reads/s, 0 avg bytes/read, 0.00 writes/s, 0.00 fsyncs/s\n" 281 | + "-------------------------------------\n" + "INSERT BUFFER AND ADAPTIVE HASH INDEX\n" + "-------------------------------------\n" 282 | + "Ibuf: size 1, free list len 5, seg size 7, 64 merges\n" + "merged operations:\n" + " insert 196, delete mark 1, delete 0\n" 283 | + "discarded operations:\n" + " insert 0, delete mark 0, delete 0\n" + "Hash table size 276707, node heap has 197 buffer(s)\n" 284 | + "0.00 hash searches/s, 0.00 non-hash searches/s\n" + "---\n" + "LOG\n" + "---\n" + "Log sequence number 11727772890\n" 285 | + "Log flushed up to 11727772890\n" + "Last checkpoint at 11727772890\n" + "0 pending log writes, 0 pending chkp writes\n" 286 | + "908045 log i/o's done, 0.00 log i/o's/second\n" + "----------------------\n" + "BUFFER POOL AND MEMORY\n" + "----------------------\n" 287 | + "Total memory allocated 137363456; in additional pool allocated 0\n" + "Dictionary memory allocated 3811224\n" + "Buffer pool size 8192\n" 288 | + "Free buffers 0\n" + "Database pages 7995\n" + "Old database pages 2931\n" + "Modified db pages 0\n" + "Pending reads 0\n" 289 | + "Pending writes: LRU 0, flush list 0, single page 0\n" + "Pages made young 5672, not young 0\n" + "0.00 youngs/s, 0.00 non-youngs/s\n" 290 | + "Pages read 4716, created 25552, written 699907\n" + "0.00 reads/s, 0.00 creates/s, 0.00 writes/s\n" 291 | + "No buffer pool page gets since the last printout\n" + "Pages read ahead 0.00/s, evicted without access 0.00/s, Random read ahead 0.00/s\n" 292 | + "LRU len: 7995, unzip_LRU len: 0\n" + "I/O sum[0]:cur[0], unzip sum[0]:cur[0]\n" + "--------------\n" + "ROW OPERATIONS\n" + "--------------\n" 293 | + "0 queries inside InnoDB, 0 queries in queue\n" + "1 read views open inside InnoDB\n" 294 | + "Main thread id 4495892480, state: waiting for server activity\n" 295 | + "Number of rows inserted 6229092, updated 1570966, deleted 199402, read 138785654\n" 296 | + "0.00 inserts/s, 0.00 updates/s, 0.00 deletes/s, 0.00 reads/s\n" + "----------------------------\n" + "END OF INNODB MONITOR OUTPUT\n" 297 | + "============================"; 298 | } 299 | -------------------------------------------------------------------------------- /test/com/newrelic/plugins/mysql/instance/TestMySQLAgent.java: -------------------------------------------------------------------------------- 1 | package com.newrelic.plugins.mysql.instance; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertFalse; 5 | import static org.junit.Assert.assertNotNull; 6 | import static org.junit.Assert.assertNull; 7 | import static org.junit.Assert.assertTrue; 8 | 9 | import java.util.HashMap; 10 | import java.util.HashSet; 11 | import java.util.Map; 12 | import java.util.Set; 13 | import java.util.concurrent.TimeUnit; 14 | 15 | import org.junit.Ignore; 16 | import org.junit.Test; 17 | 18 | /** 19 | * All enabled tests pass on version 5.5 of MySQL. 20 | * 21 | * @author Ronald Bradford me@ronaldbradford.com 22 | * 23 | */ 24 | public class TestMySQLAgent { 25 | 26 | @Ignore 27 | @Test 28 | public void verifyNewRelicMetrics() { 29 | Map results = new HashMap(); 30 | 31 | results.put("status/com_select", 1.0f); 32 | results.put("status/qcache_hits", 2.0f); 33 | results.put("status/com_insert", 4.0f); 34 | results.put("status/com_insert_select", 8.0f); 35 | results.put("status/com_update", 16.0f); 36 | results.put("status/com_update_multi", 32.0f); 37 | results.put("status/com_delete", 64.0f); 38 | results.put("status/com_delete_multi", 128.0f); 39 | results.put("status/com_replace", 256.0f); 40 | results.put("status/com_replace_select", 512.0f); 41 | 42 | Set metrics = new HashSet(); 43 | metrics.add("status"); 44 | metrics.add("newrelic"); 45 | 46 | MySQLAgent agent = new MySQLAgent("test", MySQLAgent.AGENT_DEFAULT_HOST, MySQLAgent.AGENT_DEFAULT_USER, MySQLAgent.AGENT_DEFAULT_PASSWD, 47 | MySQLAgent.AGENT_DEFAULT_PROPERTIES, metrics, new HashMap()); 48 | 49 | Map newRelicMetrics = agent.newRelicMetrics(results); 50 | assertNotNull(newRelicMetrics); 51 | assertEquals(2, newRelicMetrics.size()); 52 | assertEquals(3.0f, newRelicMetrics.get("newrelic/reads"), 0.0001f); 53 | assertEquals(1020.0f, newRelicMetrics.get("newrelic/writes"), 0.0001f); 54 | } 55 | 56 | @Test 57 | public void testExpresssions() { 58 | Map existing = new HashMap(); 59 | 60 | existing.put("status/threads_running", 4); 61 | existing.put("status/threads_connected", 10); 62 | 63 | assertEquals(50.0, (5.0 / 10.0) * 100.0, 0.0001); 64 | float threads_running = existing.get("status/threads_running").floatValue(); 65 | float threads_connected = existing.get("status/threads_connected").floatValue(); 66 | assertEquals(4.0, threads_running, 0.0001); 67 | assertEquals(10.0, threads_connected, 0.0001); 68 | assertEquals(40.0, (threads_running / threads_connected) * 100.0, 0.0001); 69 | assertEquals(4, (int) threads_running); 70 | } 71 | 72 | @Test 73 | public void testMutexMetricReporting() throws InterruptedException { 74 | 75 | Map results = new HashMap(); 76 | results.put("status/qcache_free_memory", 10.0f); 77 | results.put("innodb_mutex/log0log.c:832", 460.0f); 78 | results.put("innodb_mutex/combined buf0buf.c:916", 471.0f); 79 | 80 | Set metrics = new HashSet(); 81 | metrics.add("status"); 82 | metrics.add("newrelic"); 83 | metrics.add("innodb_mutex"); 84 | 85 | MetricTrackerMySQLAgent agent = new MetricTrackerMySQLAgent("mutex_test", MySQLAgent.AGENT_DEFAULT_HOST, MySQLAgent.AGENT_DEFAULT_USER, 86 | MySQLAgent.AGENT_DEFAULT_PASSWD, MySQLAgent.AGENT_DEFAULT_PROPERTIES, metrics, new HashMap()); 87 | 88 | agent.reportMetrics(results); 89 | 90 | // assert reported metrics only contain status/com_select 91 | assertTrue(agent.reportedMetrics.size() == 3); 92 | assertNotNull(agent.reportedMetrics.get("status/qcache_free_memory")); 93 | assertNull(agent.reportedMetrics.get("innodb_mutex/log0log.c:832")); 94 | assertNull(agent.reportedMetrics.get("innodb_mutex/combined buf0buf.c:916")); 95 | 96 | // create new results with one mutex updated value 97 | Map newResults = new HashMap(); 98 | newResults.put("status/qcache_free_memory", 15.0f); 99 | newResults.put("innodb_mutex/log0log.c:832", 460.0f); 100 | newResults.put("innodb_mutex/combined buf0buf.c:916", 480.0f); 101 | 102 | TimeUnit.SECONDS.sleep(1); 103 | 104 | // report new results 105 | agent.reportMetrics(newResults); 106 | 107 | // assert mutex metrics are not null -- the exact metric value is variable and determined by epoch counter timing 108 | assertTrue(agent.reportedMetrics.size() == 3); 109 | assertNotNull(agent.reportedMetrics.get("status/qcache_free_memory")); 110 | assertNotNull(agent.reportedMetrics.get("innodb_mutex/log0log.c:832")); 111 | assertNotNull(agent.reportedMetrics.get("innodb_mutex/combined buf0buf.c:916")); 112 | } 113 | 114 | @Test 115 | public void testIsReportingForCategory() { 116 | Set metrics = new HashSet(); 117 | metrics.add("status"); 118 | metrics.add("newrelic"); 119 | metrics.add("innodb_mutex"); 120 | 121 | MySQLAgent agent = new MySQLAgent("test", MySQLAgent.AGENT_DEFAULT_HOST, MySQLAgent.AGENT_DEFAULT_USER, MySQLAgent.AGENT_DEFAULT_PASSWD, 122 | MySQLAgent.AGENT_DEFAULT_PROPERTIES, metrics, new HashMap()); 123 | 124 | assertTrue(agent.isReportingForCategory("status")); 125 | assertTrue(agent.isReportingForCategory("newrelic")); 126 | assertTrue(agent.isReportingForCategory("innodb_mutex")); 127 | assertFalse(agent.isReportingForCategory("master")); 128 | } 129 | 130 | /** 131 | * mysql agent for tracking reported metrics to aid testing 132 | */ 133 | private static class MetricTrackerMySQLAgent extends MySQLAgent { 134 | 135 | Map reportedMetrics = new HashMap(); 136 | 137 | public MetricTrackerMySQLAgent(String name, String host, String user, String passwd, String properties, Set metrics, 138 | Map metricCategories) { 139 | super(name, host, user, passwd, properties, metrics, metricCategories); 140 | } 141 | 142 | /** 143 | * capture reported metrics for testing 144 | */ 145 | @Override 146 | public void reportMetric(String metricName, String units, Number value) { 147 | reportedMetrics.put(metricName, value); 148 | } 149 | 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /test/com/newrelic/plugins/mysql/instance/TestMySQLAgentFactory.java: -------------------------------------------------------------------------------- 1 | package com.newrelic.plugins.mysql.instance; 2 | 3 | import static com.newrelic.plugins.mysql.util.Constants.*; 4 | 5 | import static org.junit.Assert.assertEquals; 6 | import static org.junit.Assert.assertFalse; 7 | import static org.junit.Assert.assertNotNull; 8 | import static org.junit.Assert.assertNull; 9 | import static org.junit.Assert.assertTrue; 10 | import static org.junit.Assert.fail; 11 | 12 | import java.util.Arrays; 13 | import java.util.HashSet; 14 | import java.util.Map; 15 | import java.util.Set; 16 | 17 | import org.junit.Ignore; 18 | import org.junit.Test; 19 | 20 | import com.newrelic.metrics.publish.configuration.ConfigurationException; 21 | 22 | /** 23 | * All enabled tests pass on version 5.5 of MySQL. 24 | * 25 | * @author Ronald Bradford me@ronaldbradford.com 26 | * 27 | */ 28 | public class TestMySQLAgentFactory { 29 | 30 | @SuppressWarnings("unchecked") 31 | @Test 32 | public void verifyMetricCategoryConfiguration() { 33 | 34 | MySQLAgentFactory factory = new MySQLAgentFactory(); 35 | Map categories = null; 36 | try { 37 | categories = factory.readCategoryConfiguration(); 38 | } catch (ConfigurationException e) { 39 | fail(e.getMessage()); 40 | } 41 | assertEquals(7, categories.size()); 42 | Object status = categories.get("status"); 43 | assertNotNull(status); 44 | Map map = (Map) status; 45 | assertEquals("SHOW GLOBAL STATUS", map.get("SQL")); 46 | assertEquals("set", map.get("result")); 47 | 48 | Object slave = categories.get("slave"); 49 | assertNotNull(slave); 50 | map = (Map) slave; 51 | assertEquals("SHOW SLAVE STATUS", map.get("SQL")); 52 | assertEquals("row", map.get("result")); 53 | 54 | Object mutex = categories.get("innodb_mutex"); 55 | assertNotNull(mutex); 56 | map = (Map) mutex; 57 | assertEquals("SHOW ENGINE INNODB MUTEX", map.get("SQL")); 58 | assertEquals("special", map.get("result")); 59 | 60 | Object doesnotexist = categories.get("doesnotexist"); 61 | assertNull(doesnotexist); 62 | 63 | } 64 | 65 | @Ignore 66 | @Test 67 | public void verifyMetricCategoryValueAttribute() { 68 | MySQLAgentFactory factory = new MySQLAgentFactory(); 69 | Map categories = null; 70 | try { 71 | categories = factory.readCategoryConfiguration(); 72 | } catch (ConfigurationException e) { 73 | fail(e.getMessage()); 74 | } 75 | assertEquals(7, categories.size()); 76 | 77 | Object status = categories.get("status"); 78 | assertNotNull(status); 79 | @SuppressWarnings("unchecked") 80 | Map map = (Map) status; 81 | String valueMetrics = map.get("value_metrics"); 82 | assertNotNull(valueMetrics); 83 | Set metrics = new HashSet(Arrays.asList(valueMetrics.toLowerCase().split(COMMA))); 84 | assertEquals(19, metrics.size()); 85 | assertTrue(metrics.contains("uptime")); 86 | assertFalse(metrics.contains("com_select")); 87 | String counterMetrics = map.get("counter_metrics"); 88 | assertNotNull(counterMetrics); 89 | metrics = new HashSet(Arrays.asList(counterMetrics.toLowerCase().split(COMMA))); 90 | assertEquals(196, metrics.size()); 91 | 92 | } 93 | 94 | @Test 95 | public void testProcessMetricCategories() { 96 | String metrics = "status,newrelic,,innodb_mutex,"; 97 | MySQLAgentFactory factory = new MySQLAgentFactory(); 98 | 99 | Set categories = factory.processMetricCategories(metrics); 100 | 101 | assertEquals(3, categories.size()); 102 | assertTrue(categories.contains("status")); 103 | assertTrue(categories.contains("newrelic")); 104 | assertTrue(categories.contains("innodb_mutex")); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /test/lib/hamcrest-core-1.3.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newrelic/newrelic_mysql_java_plugin/97281abb3bc1b3a5be09f37631741a9769ed5e6e/test/lib/hamcrest-core-1.3.jar -------------------------------------------------------------------------------- /test/lib/junit-4.11.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/newrelic/newrelic_mysql_java_plugin/97281abb3bc1b3a5be09f37631741a9769ed5e6e/test/lib/junit-4.11.jar --------------------------------------------------------------------------------