Projet

Général

Profil

Paste
Télécharger au format
Statistiques
| Branche: | Révision:

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)