Skip to content

Commit

Permalink
Merge pull request #1793 from DataDog/garner/rebase-1274
Browse files Browse the repository at this point in the history
Add Custom MySQL query metrics
  • Loading branch information
talwai committed Jul 29, 2015
2 parents 3482245 + 25a9dd4 commit ba2ff54
Show file tree
Hide file tree
Showing 4 changed files with 62 additions and 10 deletions.
36 changes: 27 additions & 9 deletions checks.d/mysql.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
# stdlib
import os
import re
import subprocess
import os
import sys
import re
import traceback

# 3rd party
import pymysql

# project
from checks import AgentCheck
from utils.platform import Platform

# 3rd party
import pymysql

GAUGE = "gauge"
RATE = "rate"

Expand Down Expand Up @@ -52,6 +52,8 @@

class MySql(AgentCheck):
SERVICE_CHECK_NAME = 'mysql.can_connect'
MAX_CUSTOM_QUERIES = 20
DEFAULT_TIMEOUT = 5

def __init__(self, name, init_config, agentConfig, instances=None):
AgentCheck.__init__(self, name, init_config, agentConfig, instances)
Expand All @@ -62,9 +64,11 @@ def get_library_versions(self):
return {"pymysql": pymysql.__version__}

def check(self, instance):
host, port, user, password, mysql_sock, defaults_file, tags, options = \
host, port, user, password, mysql_sock, defaults_file, tags, options, queries = \
self._get_config(instance)

default_timeout = self.init_config.get('default_timeout', self.DEFAULT_TIMEOUT)

if (not host or not user) and not defaults_file:
raise Exception("Mysql host and user are needed.")

Expand All @@ -74,7 +78,7 @@ def check(self, instance):
self._collect_metadata(db, host)

# Metric collection
self._collect_metrics(host, db, tags, options)
self._collect_metrics(host, db, tags, options, queries)
if Platform.is_linux():
self._collect_system_metrics(host, db, tags)

Expand All @@ -87,8 +91,9 @@ def _get_config(self, instance):
defaults_file = instance.get('defaults_file', '')
tags = instance.get('tags', None)
options = instance.get('options', {})
queries = instance.get('queries', [])

return host, port, user, password, mysql_sock, defaults_file, tags, options
return host, port, user, password, mysql_sock, defaults_file, tags, options, queries

def _connect(self, host, port, mysql_sock, user, password, defaults_file):
service_check_tags = [
Expand Down Expand Up @@ -132,7 +137,7 @@ def _connect(self, host, port, mysql_sock, user, password, defaults_file):

return db

def _collect_metrics(self, host, db, tags, options):
def _collect_metrics(self, host, db, tags, options, queries):
cursor = db.cursor()
cursor.execute("SHOW /*!50002 GLOBAL */ STATUS;")
status_results = dict(cursor.fetchall())
Expand Down Expand Up @@ -191,6 +196,17 @@ def _collect_metrics(self, host, db, tags, options):
"SHOW SLAVE STATUS", db, tags=tags
)

# Collect custom query metrics
# Max of 20 queries allowed
if isinstance(queries, list):
for index, check in enumerate(queries[:self.MAX_CUSTOM_QUERIES]):
self._collect_dict(check['type'], {check['field']: check['metric']}, check['query'], db, tags=tags)

if len(queries) > self.MAX_CUSTOM_QUERIES:
self.warning("Maximum number (%s) of custom queries reached. Skipping the rest."
% self.MAX_CUSTOM_QUERIES)


def _collect_metadata(self, db, host):
self._get_version(db, host)

Expand Down Expand Up @@ -287,7 +303,9 @@ def _collect_dict(self, metric_type, field_metric_map, query, db, tags):
# cursor.description is a tuple of (column_name, ..., ...)
try:
col_idx = [d[0].lower() for d in cursor.description].index(field.lower())
self.log.debug("Collecting metric: %s" % metric)
if result[col_idx] is not None:
self.log.debug("Collecting done, value %s" % result[col_idx])
if metric_type == GAUGE:
self.gauge(metric, float(result[col_idx]), tags=tags)
elif metric_type == RATE:
Expand Down
5 changes: 5 additions & 0 deletions ci/mysql.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@

task before_script: ['ci:common:before_script'] do
sh %(mysql -e "create user 'dog'@'localhost' identified by 'dog'" -uroot)
sh %(mysql -e "CREATE DATABASE testdb;" -uroot)
sh %(mysql -e "CREATE TABLE testdb.users (name VARCHAR(20), age INT);" -uroot)
sh %(mysql -e "GRANT SELECT ON testdb.users TO 'dog'@'localhost';" -uroot)
sh %(mysql -e "INSERT INTO testdb.users (name,age) VALUES('Alice',25);" -uroot)
sh %(mysql -e "INSERT INTO testdb.users (name,age) VALUES('Bob',20);" -uroot)
end

task script: ['ci:common:script'] do
Expand Down
11 changes: 11 additions & 0 deletions conf.d/mysql.yaml.example
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,14 @@ instances:
# options: # Optional
# replication: 0
# galera_cluster: 1
# queries: # Optional
# - # Sample Custom metric
# query: SELECT TIMESTAMPDIFF(second,MAX(create_time),NOW()) as last_accessed FROM requests
# metric: app.seconds_since_last_request
# type: gauge
# field: last_accessed
# - # Sample Custom metric
# query: SELECT TIMESTAMPDIFF(second,MAX(create_time),NOW()) as last_user FROM users
# metric: app.seconds_since_new_user
# type: gauge
# field: last_user
20 changes: 19 additions & 1 deletion tests/checks/integration/test_mysql.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,21 @@ class TestMySql(AgentCheckTest):
'user': 'dog',
'pass': 'dog',
'options': {'replication': True},
'tags': METRIC_TAGS
'tags': METRIC_TAGS,
'queries': [
{
'query': "SELECT * from testdb.users where name='Alice' limit 1;",
'metric': 'alice.age',
'type': 'gauge',
'field': 'age'
},
{
'query': "SELECT * from testdb.users where name='Bob' limit 1;",
'metric': 'bob.age',
'type': 'gauge',
'field': 'age'
}
]
}]

CONNECTION_FAILURE = [{
Expand Down Expand Up @@ -117,6 +131,10 @@ def test_check(self):
# Assert service metadata
self.assertServiceMetadata(['version'], count=1)

#test custom query metrics
self.assertMetric('alice.age', value=25)
self.assertMetric('bob.age', value=20)

# Raises when COVERAGE=true and coverage < 100%
self.coverage_report()

Expand Down

0 comments on commit ba2ff54

Please sign in to comment.