root / plugins / disk / scsi_queue @ 14e5261e
Historique | Voir | Annoter | Télécharger (8,22 ko)
| 1 | db0871ee | Troels Arvin | #!/usr/bin/env python |
|---|---|---|---|
| 2 | |||
| 3 | """ |
||
| 4 | 17f78427 | Lars Kruse | Munin plugin which reports queue busy-values per online SCSI |
| 5 | db0871ee | Troels Arvin | 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 | 17f78427 | Lars Kruse | If you symlink the plugin, so that it's executed as |
| 15 | db0871ee | Troels Arvin | 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 | 17f78427 | Lars Kruse | # ==================================================================== |
| 31 | db0871ee | Troels Arvin | # 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 | 17f78427 | Lars Kruse | # ==================================================================== |
| 56 | db0871ee | Troels Arvin | |
| 57 | 4323adbf | Troels Arvin | # $Id: scsi_queue 13630 2010-08-31 15:29:14Z tra $ |
| 58 | db0871ee | Troels Arvin | |
| 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 | 4323adbf | Troels Arvin | if filter_from and filter_through: |
| 88 | db0871ee | Troels Arvin | 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 | 17f78427 | Lars Kruse | # The colon-separated values map to the first four parts |
| 148 | db0871ee | Troels Arvin | # of /proc/scsi/sg/devices |
| 149 | 17f78427 | Lars Kruse | # And the directory entries are symlinks which point to directories |
| 150 | # in /sys/devices. By following a symlink, we may end up in |
||
| 151 | db0871ee | Troels Arvin | # 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 | fba800ae | Veres Lajos | # Should actually not happen, but nonetheless: |
| 160 | db0871ee | Troels Arvin | if not os.path.islink(sys_pathname): |
| 161 | continue |
||
| 162 | |||
| 163 | # Search for dirent called block:SOMETHING |
||
| 164 | # Put SOMETHING into blockdev_name |
||
| 165 | 17f78427 | Lars Kruse | # Couldn't make glob.glob() work: The length of the result |
| 166 | db0871ee | Troels Arvin | # 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 | 4323adbf | Troels Arvin | filter_from = None |
| 221 | filter_through = None |
||
| 222 | db0871ee | Troels Arvin | 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) |
