root / plugins / disk / scsi_queue @ 17f78427
Historique | Voir | Annoter | Télécharger (8,22 ko)
| 1 |
#!/usr/bin/env python |
|---|---|
| 2 |
|
| 3 |
""" |
| 4 |
Munin plugin which reports queue busy-values per online SCSI |
| 5 |
device on Linux, as seen in /proc/scsi/sg/devices |
| 6 |
|
| 7 |
If the busy-values often reach the queue depth of the device, |
| 8 |
one might consider increasing the queue depth. Hence, this |
| 9 |
plugin. |
| 10 |
|
| 11 |
Wildcard use: |
| 12 |
If your system has many SCSI-like devices, filtering may be needed |
| 13 |
to make the resulting graphs readable. |
| 14 |
If you symlink the plugin, so that it's executed as |
| 15 |
scsi_queue_X_through_Y |
| 16 |
then the plugin will only look at devices |
| 17 |
/dev/sdX .. /dev/sdY |
| 18 |
X and Y may only be one-character values. |
| 19 |
X and Y are translated into a regular expression like: |
| 20 |
sd[X-Y] |
| 21 |
""" |
| 22 |
|
| 23 |
# Author: Troels Arvin <tra@sst.dk> |
| 24 |
# See http://troels.arvin.dk/code/munin/ for latest version. |
| 25 |
|
| 26 |
# Only tested with Red Hat Enterprise Linux 5 / CentOS 5, currently. |
| 27 |
|
| 28 |
# Released according to the "New BSD License" AKA the 3-clause |
| 29 |
# BSD License: |
| 30 |
# ==================================================================== |
| 31 |
# Copyright (c) 2010, Danish National Board of Health. |
| 32 |
# All rights reserved. |
| 33 |
# |
| 34 |
# Redistribution and use in source and binary forms, with or without |
| 35 |
# modification, are permitted provided that the following conditions are met: |
| 36 |
# * Redistributions of source code must retain the above copyright |
| 37 |
# notice, this list of conditions and the following disclaimer. |
| 38 |
# * Redistributions in binary form must reproduce the above copyright |
| 39 |
# notice, this list of conditions and the following disclaimer in the |
| 40 |
# documentation and/or other materials provided with the distribution. |
| 41 |
# * Neither the name of the the Danish National Board of Health nor the |
| 42 |
# names of its contributors may be used to endorse or promote products |
| 43 |
# derived from this software without specific prior written permission. |
| 44 |
# |
| 45 |
# THIS SOFTWARE IS PROVIDED BY the Danish National Board of Health ''AS IS'' AND ANY |
| 46 |
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| 47 |
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| 48 |
# DISCLAIMED. IN NO EVENT SHALL the Danish National Board of Health BE LIABLE FOR ANY |
| 49 |
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| 50 |
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| 51 |
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| 52 |
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 53 |
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
| 54 |
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 55 |
# ==================================================================== |
| 56 |
|
| 57 |
# $Id: scsi_queue 13630 2010-08-31 15:29:14Z tra $ |
| 58 |
|
| 59 |
# Note to self: |
| 60 |
# The fields in /proc/scsi/sg/devices are: |
| 61 |
# host chan id lun type opens qdepth busy online |
| 62 |
|
| 63 |
# TODO: |
| 64 |
# - Make it possible to group by multipath group. Might be |
| 65 |
# hard, though, because determining path groups seems |
| 66 |
# to require root privileges. |
| 67 |
# - Support autoconf |
| 68 |
# - How to support filtering on installations which have |
| 69 |
# many SCSI devices, beyond /dev/sdz? |
| 70 |
|
| 71 |
import os, sys, re |
| 72 |
|
| 73 |
procfile = '/proc/scsi/sg/devices' |
| 74 |
sysfs_base = '/sys/bus/scsi/devices' |
| 75 |
|
| 76 |
my_canonical_name = 'scsi_queue' # If called as - e.g. - scsi_queue_foo, then |
| 77 |
# foo will be interpreted as a device filter. |
| 78 |
# For this, we need a base name. |
| 79 |
|
| 80 |
def bailout(msg): |
| 81 |
sys.stderr.write(msg+"\n") |
| 82 |
sys.exit(1) |
| 83 |
|
| 84 |
def print_config(devices,filter_from,filter_through): |
| 85 |
|
| 86 |
title_qualification = '' |
| 87 |
if filter_from and filter_through: |
| 88 |
title_qualification = ' for devices sd%s through sd%s' % (filter_from,filter_through) |
| 89 |
|
| 90 |
print 'graph_title SCSI queue busy values' + title_qualification |
| 91 |
print 'graph_vlabel busy count' |
| 92 |
print 'graph_args --base 1000 -l 0' |
| 93 |
print 'graph_category disk' |
| 94 |
print 'graph_info This graph shows the queue busy values, as seen in /prod/scsi/sg/devices' |
| 95 |
|
| 96 |
keys = devices.keys() |
| 97 |
keys.sort() |
| 98 |
for key in keys: |
| 99 |
qdepth = devices[key]['qdepth'] |
| 100 |
print '%s.min 0' % key |
| 101 |
print '%s.type GAUGE' % key |
| 102 |
print '%s.label %s (%s %s); qdepth=%s' % ( |
| 103 |
key, |
| 104 |
key, |
| 105 |
devices[key]['vendor'], |
| 106 |
devices[key]['model'], |
| 107 |
qdepth |
| 108 |
) |
| 109 |
print '%s.max %s' % (key,qdepth) |
| 110 |
|
| 111 |
# Return a list of lists representing interesting parts from procfile |
| 112 |
def parse_procfile(): |
| 113 |
retval = [] |
| 114 |
try: |
| 115 |
fh = open(procfile) |
| 116 |
for line in fh: |
| 117 |
retval.append(line.split()) |
| 118 |
|
| 119 |
except IOError, e: |
| 120 |
bailout('IO error: '+str(e))
|
| 121 |
return retval |
| 122 |
|
| 123 |
# Try to read a file's content. If any I/O problem: return empty string |
| 124 |
def readfile(path): |
| 125 |
try: |
| 126 |
f = open(path) |
| 127 |
retval = f.read().rstrip() |
| 128 |
f.close() |
| 129 |
except IOError, e: |
| 130 |
return '' |
| 131 |
return retval |
| 132 |
|
| 133 |
# Return dict of dicts, indexed by device name |
| 134 |
def map_procentries_to_devices(list_of_dicts,devfilter_regex): |
| 135 |
device_dict={}
|
| 136 |
|
| 137 |
if devfilter_regex: |
| 138 |
regex_compiled = re.compile(devfilter_regex) |
| 139 |
|
| 140 |
for elem in list_of_dicts: |
| 141 |
# In /sys/bus/scsi/devices we see a number of directory |
| 142 |
# entries, such as: |
| 143 |
# 0:0:0:0 |
| 144 |
# 2:0:0:0 |
| 145 |
# 3:0:0:0 |
| 146 |
# |
| 147 |
# The colon-separated values map to the first four parts |
| 148 |
# of /proc/scsi/sg/devices |
| 149 |
# And the directory entries are symlinks which point to directories |
| 150 |
# in /sys/devices. By following a symlink, we may end up in |
| 151 |
# a directory which contains directory entries like: |
| 152 |
# - block:sdb |
| 153 |
# ... |
| 154 |
# - model |
| 155 |
# ... |
| 156 |
# - vendor |
| 157 |
sys_pathname = sysfs_base + '/' + ':'.join(elem[:4]) # isolate stuff like 2:0:0:0 |
| 158 |
|
| 159 |
# Should actually not happen, but nonetheless: |
| 160 |
if not os.path.islink(sys_pathname): |
| 161 |
continue |
| 162 |
|
| 163 |
# Search for dirent called block:SOMETHING |
| 164 |
# Put SOMETHING into blockdev_name |
| 165 |
# Couldn't make glob.glob() work: The length of the result |
| 166 |
# of glob() returned TypeError: len() of unsized object on |
| 167 |
# RHEL 5's python... |
| 168 |
dirents = os.listdir(sys_pathname) |
| 169 |
num_blocklines=0 |
| 170 |
for dirent in dirents: |
| 171 |
if dirent.startswith('block:'):
|
| 172 |
block_line = dirent |
| 173 |
num_blocklines += 1 |
| 174 |
if num_blocklines == 0: |
| 175 |
continue |
| 176 |
if num_blocklines > 1: |
| 177 |
bailout("Got more than one result when globbing for '%s'" % glob_for)
|
| 178 |
blockdev_name = block_line.split(':')[1]
|
| 179 |
|
| 180 |
# If device filtering is active, filter now |
| 181 |
if devfilter_regex: |
| 182 |
if not regex_compiled.match(blockdev_name): |
| 183 |
continue |
| 184 |
|
| 185 |
# Merge info from the /proc and /sys sources |
| 186 |
device_dict[blockdev_name] = {
|
| 187 |
'model' : readfile(sys_pathname+'/model'), |
| 188 |
'vendor': readfile(sys_pathname+'/vendor'), |
| 189 |
'qdepth': elem[6], |
| 190 |
'busy' : elem[7] |
| 191 |
} |
| 192 |
return device_dict |
| 193 |
|
| 194 |
def print_values(devices): |
| 195 |
devnames = devices.keys() |
| 196 |
devnames.sort() |
| 197 |
retval = '' |
| 198 |
for devname in devnames: |
| 199 |
print "%s.value %s" % ( |
| 200 |
devname, |
| 201 |
devices[devname]['busy'] |
| 202 |
) |
| 203 |
|
| 204 |
|
| 205 |
|
| 206 |
|
| 207 |
# Initial sanity check |
| 208 |
n_args=len(sys.argv) |
| 209 |
if n_args > 2: |
| 210 |
# At most one arg expected |
| 211 |
print '%d arguments given - expecting only one' % n_args |
| 212 |
sys.exit(1) |
| 213 |
|
| 214 |
# See if we were called with a Munin wildcard-style 'arg0-argument' |
| 215 |
# E.g., if called as scsi_queue_a_through_c, then consider only |
| 216 |
# devices sda, sdb, sdc. |
| 217 |
devfilter_regex = None |
| 218 |
called_as = os.path.basename(sys.argv[0]) |
| 219 |
match = re.match(my_canonical_name+'_([^_])_through_([^_])', called_as) |
| 220 |
filter_from = None |
| 221 |
filter_through = None |
| 222 |
if match: |
| 223 |
filter_from = match.group(1) |
| 224 |
filter_through = match.group(2) |
| 225 |
devfilter_regex = 'sd['+filter_from+'-'+filter_through+']' |
| 226 |
|
| 227 |
# Perform main piece of work |
| 228 |
devices = map_procentries_to_devices( |
| 229 |
parse_procfile(), |
| 230 |
devfilter_regex |
| 231 |
) |
| 232 |
|
| 233 |
# See how we were called |
| 234 |
if n_args == 2: |
| 235 |
# An argument was given, so let's not simply print |
| 236 |
# values. |
| 237 |
arg = sys.argv[1] |
| 238 |
if arg == 'config': |
| 239 |
print_config(devices,filter_from,filter_through) |
| 240 |
sys.exit(0) |
| 241 |
else: |
| 242 |
print "Unknown argument '%s'" % arg |
| 243 |
sys.exit(1) |
| 244 |
|
| 245 |
# No arguments given; print values |
| 246 |
print_values(devices) |
