├── .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 | [](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
--------------------------------------------------------------------------------