Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Custom MySQL query metrics #1793

Merged
merged 3 commits into from
Jul 29, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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