155 lines
6.2 KiB
Python
155 lines
6.2 KiB
Python
# (C) Copyright 2015-2016 Hewlett Packard Enterprise Development LP
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
"""Check the performance counters from SQL Server.
|
|
|
|
"""
|
|
import traceback
|
|
|
|
from monasca_agent.collector.checks import AgentCheck
|
|
|
|
|
|
ALL_INSTANCES = 'ALL'
|
|
VALID_METRIC_TYPES = ('gauge', 'rate')
|
|
|
|
|
|
class SQLServer(AgentCheck):
|
|
METRICS = [
|
|
('sqlserver.buffer.cache_hit_ratio', 'gauge', 'Buffer cache hit ratio'),
|
|
('sqlserver.buffer.page_life_expectancy', 'gauge', 'Page life expectancy'),
|
|
('sqlserver.stats.batch_requests', 'gauge', 'Batch Requests/sec'),
|
|
('sqlserver.stats.sql_compilations', 'gauge', 'SQL Compilations/sec'),
|
|
('sqlserver.stats.sql_recompilations', 'gauge', 'SQL Re-Compilations/sec'),
|
|
('sqlserver.stats.connections', 'gauge', 'User connections'),
|
|
('sqlserver.stats.lock_waits', 'gauge', 'Lock Waits/sec', '_Total'),
|
|
('sqlserver.access.page_splits', 'gauge', 'Page Splits/sec'),
|
|
('sqlserver.stats.procs_blocked', 'gauge', 'Processes Blocked'),
|
|
('sqlserver.buffer.checkpoint_pages', 'gauge', 'Checkpoint pages/sec')
|
|
]
|
|
|
|
def __init__(self, name, init_config, agent_config):
|
|
AgentCheck.__init__(self, name, init_config, agent_config)
|
|
|
|
# Load any custom metrics from conf.d/sqlserver.yaml
|
|
for row in init_config.get('custom_metrics', []):
|
|
if row['type'] not in VALID_METRIC_TYPES:
|
|
self.log.error('%s has an invalid metric type: %s' % (row['name'], row['type']))
|
|
self.METRICS.append((row['name'], row['type'], row['counter_name'],
|
|
row.get('instance_name', ''), row.get('tag_by', None)))
|
|
|
|
# Cache connections
|
|
self.connections = {}
|
|
|
|
@staticmethod
|
|
def _conn_key(host, username, password, database):
|
|
"""Return a key to use for the connection cache.
|
|
|
|
"""
|
|
return '%s:%s:%s:%s' % (host, username, password, database)
|
|
|
|
@staticmethod
|
|
def _conn_string(host, username, password, database):
|
|
"""Return a connection string to use with adodbapi.
|
|
|
|
"""
|
|
conn_str = 'Provider=SQLOLEDB;Data Source=%s;Initial Catalog=%s;' % (host, database)
|
|
if username:
|
|
conn_str += 'User ID=%s;' % (username)
|
|
if password:
|
|
conn_str += 'Password=%s;' % (password)
|
|
if not username and not password:
|
|
conn_str += 'Integrated Security=SSPI;'
|
|
return conn_str
|
|
|
|
def check(self, instance):
|
|
try:
|
|
import adodbapi
|
|
except ImportError:
|
|
raise Exception("Unable to import adodbapi module.")
|
|
|
|
host = instance.get('host', '127.0.0.1;1433')
|
|
username = instance.get('username')
|
|
password = instance.get('password')
|
|
database = instance.get('database', 'master')
|
|
conn_key = self._conn_key(host, username, password, database)
|
|
dimensions = self._set_dimensions(None, instance)
|
|
|
|
if conn_key not in self.connections:
|
|
try:
|
|
conn_str = self._conn_string(host, username, password, database)
|
|
conn = adodbapi.connect(conn_str)
|
|
self.connections[conn_key] = conn
|
|
except Exception:
|
|
cx = "%s - %s" % (host, database)
|
|
raise Exception("Unable to connect to SQL Server for instance %s.\n %s"
|
|
% (cx, traceback.format_exc()))
|
|
|
|
conn = self.connections[conn_key]
|
|
cursor = conn.cursor()
|
|
self._fetch_metrics(cursor, dimensions)
|
|
|
|
def _fetch_metrics(self, cursor, custom_dimensions):
|
|
"""Fetch the metrics from the sys.dm_os_performance_counters table.
|
|
|
|
"""
|
|
for metric in self.METRICS:
|
|
# Normalize all rows to the same size for easy of use
|
|
if len(metric) == 3:
|
|
metric = metric + ('', None)
|
|
elif len(metric) == 4:
|
|
metric = metric + (None,)
|
|
|
|
mname, mtype, counter, instance_n, tag_by = metric
|
|
|
|
# For "ALL" instances, we run a separate method because we have
|
|
# to loop over multiple results and tag the metrics
|
|
if instance_n == ALL_INSTANCES:
|
|
try:
|
|
self._fetch_all_instances(metric, cursor, custom_dimensions)
|
|
except Exception:
|
|
self.log.exception('Unable to fetch metric: %s' % mname)
|
|
self.log.warn('Unable to fetch metric: %s' % mname)
|
|
else:
|
|
try:
|
|
cursor.execute("""
|
|
select cntr_value
|
|
from sys.dm_os_performance_counters
|
|
where counter_name = ?
|
|
and instance_name = ?
|
|
""", (counter, instance_n))
|
|
(value,) = cursor.fetchone()
|
|
except Exception:
|
|
self.log.exception('Unable to fetch metric: %s' % mname)
|
|
self.log.warn('Unable to fetch metric: %s' % mname)
|
|
continue
|
|
|
|
# Save the metric
|
|
metric_func = getattr(self, mtype)
|
|
metric_func(mname, value, dimensions=custom_dimensions)
|
|
|
|
def _fetch_all_instances(self, metric, cursor, dimensions):
|
|
mname, mtype, counter, instance_n, tag_by = metric
|
|
cursor.execute("""
|
|
select instance_name, cntr_value
|
|
from sys.dm_os_performance_counters
|
|
where counter_name = ?
|
|
and instance_name != '_Total'
|
|
""", (counter,))
|
|
rows = cursor.fetchall()
|
|
|
|
for instance_name, cntr_value in rows:
|
|
value = cntr_value
|
|
dimensions.update({tag_by: instance_name.strip()})
|
|
metric_func = getattr(self, mtype)
|
|
metric_func(mname, value, dimensions=dimensions)
|