├── .gitignore
├── LICENSE
├── README.md
├── collectd_iostat_python.py
└── iostat_types.db
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 |
5 | # C extensions
6 | *.so
7 |
8 | # Distribution / packaging
9 | .Python
10 | env/
11 | bin/
12 | build/
13 | develop-eggs/
14 | dist/
15 | eggs/
16 | lib/
17 | lib64/
18 | parts/
19 | sdist/
20 | var/
21 | *.egg-info/
22 | .installed.cfg
23 | *.egg
24 |
25 | # Installer logs
26 | pip-log.txt
27 | pip-delete-this-directory.txt
28 |
29 | # Unit test / coverage reports
30 | htmlcov/
31 | .tox/
32 | .coverage
33 | .cache
34 | nosetests.xml
35 | coverage.xml
36 |
37 | # Translations
38 | *.mo
39 |
40 | # Mr Developer
41 | .mr.developer.cfg
42 | .project
43 | .pydevproject
44 |
45 | # Rope
46 | .ropeproject
47 |
48 | # Django stuff:
49 | *.log
50 | *.pot
51 |
52 | # Sphinx documentation
53 | docs/_build/
54 |
55 | # idea
56 | .idea
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014-2016 Denis Zhdanov
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | collectd-iostat-python
2 | ======================
3 |
4 | `collectd-iostat-python` is an `iostat` plugin for Collectd that allows you to
5 | graph Linux `iostat` metrics in Graphite or other output formats that are
6 | supported by Collectd.
7 |
8 | This plugin (and mostly this `README`) is rewrite of the
9 | [collectd-iostat](https://github.com/keirans/collectd-iostat) from Ruby to Python
10 | using the
11 | [collectd-python](http://collectd.org/documentation/manpages/collectd-python.5.shtml)
12 | plugin.
13 |
14 |
15 | Why?
16 | ----
17 |
18 | Disk performance is quite crucial for most of modern server
19 | applications, especially databases (e.g. MySQL - check out [this
20 | slides](http://www.percona.com/live/mysql-conference-2013/sessions/monitoring-io-performance-using-iostat-and-pt-diskstats)
21 | from Percona Live conference).
22 |
23 | Although Collectd [provides disk statistics out of the
24 | box](https://collectd.org/wiki/index.php/Plugin:Disk), graphing the metrics as
25 | shown by `iostat` was found to be more useful and graphic, because `iostat`
26 | reports usage of block devices, partitions, multipath devices and LVM volumes.
27 |
28 | Also this plugin was rewritten in Python, because it's a preferable language for
29 | siteops' tools on my current job, and choice of using
30 | [collectd-python](http://collectd.org/documentation/manpages/collectd-python.5.shtml)
31 | instead of
32 | [collectd-exec](https://collectd.org/documentation/manpages/collectd-exec.5.shtml)
33 | was made for performance and stability reasons.
34 |
35 |
36 | How?
37 | ----
38 |
39 | `collectd-iostat-python` functions by calling `iostat` with some predefined
40 | intervals and push that data to Collectd using Collectd `python` plugin.
41 |
42 | Collectd can be then configured to write the Collected data into many output
43 | formats that are supported by it's write plugins, such as `graphite`, which was
44 | the primary use case for this plugin.
45 |
46 |
47 | Setup
48 | -----
49 |
50 | Deploy the Collectd Python plugin into a suitable plugin directory for your
51 | Collectd instance.
52 |
53 | Configure Collectd's `python` plugin to execute the `iostat` plugin using a
54 | stanza similar to the following:
55 |
56 |
57 | ```
58 |
59 | Globals true
60 |
61 |
62 |
63 | ModulePath "/usr/lib/collectd/plugins/python"
64 | Import "collectd_iostat_python"
65 |
66 |
67 | Path "/usr/bin/iostat"
68 | Interval 2
69 | Count 2
70 | Verbose false
71 | NiceNames false
72 | PluginName collectd_iostat_python
73 |
74 |
75 | ```
76 |
77 | If you need to select a subset of the devices listed by iostat you can utilize
78 | `DisksRegex` to write a regular expression to select the appropriate devices for your environment.
79 | ```
80 | # Only collect data for these devices
81 | DisksRegex "^[hs]d"
82 | ```
83 |
84 | In large and changing environments it benefital if your device statistics maintain the same device names across reboots or reconfigurations so that historical data is not compromised. This can be achived by enabling persistent naming based on udev attributes.
85 | Simply enable persistent naming by setting UdevNameAttr to the attribute you want to use to name your devices. A good example would be ID_SERIAL which is persistent and unique per device. To find useful attributes you can use `udevadm info /dev/`
86 | ```
87 | # Enable persistent device naming
88 | UdevNameAttr "ID_SERIAL"
89 | ```
90 |
91 | Please note that you need to install [pyudev](https://pyudev.readthedocs.io/en/latest/) Python module for this functionality.
92 |
93 | In a multipath environment, a single physical disk can be exposed as two /dev entries. A device mapper entry is created by multipathd to handle interacting with the disk. Setting SkipPhysicalMultipath causes the physical multipath disks to be skipped, and only the dm- entry to be processed. Physical non-multipathed disks will be processed normally. Enable NoDisplayDMName as well to display the /dev entry instead of the registered device name.
94 |
95 | ```
96 | SkipPhysicalMultipath true
97 | NoDisplayDMName true
98 | ```
99 |
100 | Please note that you need to install [pyudev](https://pyudev.readthedocs.io/en/latest/) Python module for this functionality.
101 |
102 |
103 | If you would like to use more legible metric names (e.g.
104 | `requests_merged_per_second-read` instead of `rrqm_s`), you have to set
105 | `NiceNames` to `true` and add load the custom types database (see the
106 | `iostat_types.db` file) by adding the following into the Collectd config file:
107 |
108 | ```
109 | # The default Collectd types database
110 | TypesDB "/usr/share/collectd/types.db"
111 | # The custom types database
112 | TypesDB "/usr/share/collectd/iostat_types.db"
113 | ```
114 |
115 | Once functioning, the `iostat` data should then be visible via your various
116 | output plugins. Please note, that you need to restart collectd service after
117 | plugin installation, as usual.
118 |
119 | In the case of Graphite, Collectd should be writing data in the
120 | `hostname_domain_tld.collectd_iostat_python.DEVICE.column-name` style namespaces.
121 | Symbols like `/`, `-` and `%` in metric names (but not in device names) are
122 | automatically replaced by underscores (i.e. `_`).
123 |
124 | Please note that this plugin will take only last line of `iostat`
125 | output, so big `Count` numbers also have no sense, but `Count` needs
126 | to be more than `1` to get actual and not historical data. The data
127 | collection will run every `Interval * (Count-1)` seconds and this
128 | value needs to be less than `Collectd.INTERVAL`. Default
129 | `Collectd.INTERVAL` is 10 seconds, so default value of `Count=2` and
130 | `Interval=2` works quite well for me.
131 |
132 |
133 | Technical notes
134 | ---------------
135 |
136 | For parsing `iostat` output, I'm using
137 | [jakamkon's](https://bitbucket.org/jakamkon)
138 | [python-iostat](https://bitbucket.org/jakamkon/python-iostat) Python module, but
139 | as an internal part of the script instead of a separate module because of couple
140 | of fixes I have made (using Kbytes instead of blocks, adding -N to `iostat` for
141 | LVM endpoint resolving, migration to `subprocess` module as replacement of
142 | deprecated `popen3`, `objectification` etc).
143 |
144 |
145 | Compatibility
146 | -------------
147 |
148 | Plugin was tested on Ubuntu 12.04/14.04 (Collectd 5.2/5.3/5.4, Python 2.7) and
149 | CentOS (Collectd 5.4 / Python 2.6). Please note that if running Python 2.6 or
150 | older (i.e. on CentOS and its derivatives) we trying to restore `SIGCHLD` signal
151 | handler to mitigate a known [bug](http://bugs.python.org/issue1731717) which
152 | according to the Collectd's
153 | [documentation](https://collectd.org/documentation/manpages/collectd-python.5.shtml#configuration)
154 | breaks the `exec` plugin, unfortunately.
155 |
156 |
157 | TODO
158 | ----
159 |
160 | * Maybe some data aggregation needed (e.g. we can use some max / avg aggregation
161 | of data across intervals instead of picking last line of `iostat` output).
162 | * The `Disks` parameter could support regexp.
163 |
164 |
165 | Additional reading
166 | ------------------
167 |
168 | * [man iostat(1)](http://linux.die.net/man/1/iostat)
169 | * [Custom Collectd Plug-ins for Linux](http://support.rightscale.com/12-Guides/RightScale_101/08-Management_Tools/Monitoring_System/Writing_custom_collectd_plugins/Custom_Collectd_Plug-ins_for_Linux)
170 | * [python-iostat](https://bitbucket.org/jakamkon/python-iostat)
171 | * [collectd-iostat](https://github.com/keirans/collectd-iostat)
172 | * [Graphite @ The Architecture of Open Source Applications](http://www.aosabook.org/en/graphite.html)
173 |
174 | License
175 | -------
176 |
177 | [MIT](http://mit-license.org/)
178 |
179 |
180 | Support
181 | -------
182 |
183 | Please do not send me PMs in Twitter with issues. Just open an [issue](https://github.com/deniszh/collectd-iostat-python/issues) on [projects' Github](https://github.com/deniszh/collectd-iostat-python) instead and I'll respond ASAP!
184 |
185 |
186 | Contact
187 | -------
188 |
189 | [Denis Zhdanov](mailto:denis.zhdanov@gmail.com)
190 | ([@deniszh](http://twitter.com/deniszh))
191 |
--------------------------------------------------------------------------------
/collectd_iostat_python.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # coding=utf-8
3 | #
4 | # collectd-iostat-python
5 | # ======================
6 | #
7 | # Collectd-iostat-python is an iostat plugin for collectd that allows you to
8 | # graph Linux iostat metrics in Graphite or other output formats that are
9 | # supported by collectd.
10 | #
11 | # https://github.com/powdahound/redis-collectd-plugin
12 | # - was used as template
13 | # https://github.com/keirans/collectd-iostat/
14 | # - was used as inspiration and contains some code from
15 | # https://bitbucket.org/jakamkon/python-iostat
16 | # - by Kuba Kończyk
17 | #
18 |
19 | import signal
20 | import subprocess
21 | import sys
22 | import re
23 | try:
24 | import pyudev
25 | pyudev_available = True
26 | except ImportError:
27 | pyudev_available = False
28 |
29 | try:
30 | maketrans = ''.maketrans
31 | except AttributeError:
32 | # fallback for Python 2
33 | from string import maketrans
34 |
35 | __version__ = '0.0.5'
36 | __author__ = 'denis.zhdanov@gmail.com'
37 |
38 |
39 | class IOStatError(Exception):
40 | pass
41 |
42 |
43 | class CmdError(IOStatError):
44 | pass
45 |
46 |
47 | class ParseError(IOStatError):
48 | pass
49 |
50 |
51 | class IOStat(object):
52 | def __init__(self, path='/usr/bin/iostat', interval=2, count=2, disks=[], no_dm_name=False):
53 | self.path = path
54 | self.interval = interval
55 | self.count = count
56 | self.disks = disks
57 | self.no_dm_name = no_dm_name
58 |
59 | def parse_diskstats(self, input):
60 | """
61 | Parse iostat -d and -dx output.If there are more
62 | than one series of statistics, get the last one.
63 | By default parse statistics for all avaliable block devices.
64 |
65 | @type input: C{string}
66 | @param input: iostat output
67 |
68 | @type disks: list of C{string}s
69 | @param input: lists of block devices that
70 | statistics are taken for.
71 |
72 | @return: C{dictionary} contains per block device statistics.
73 | Statistics are in form of C{dictonary}.
74 | Main statistics:
75 | tps Blk_read/s Blk_wrtn/s Blk_read Blk_wrtn
76 | Extended staistics (available with post 2.5 kernels):
77 | rrqm/s wrqm/s r/s w/s rsec/s wsec/s rkB/s wkB/s avgrq-sz \
78 | avgqu-sz await svctm %util
79 | See I{man iostat} for more details.
80 | """
81 | dstats = {}
82 | dsi = input.rfind('Device')
83 | if dsi == -1:
84 | raise ParseError('Unknown input format: %r' % input)
85 |
86 | ds = input[dsi:].splitlines()
87 | hdr = ds.pop(0).split()[1:]
88 |
89 | for d in ds:
90 | if d:
91 | d = d.split()
92 | d = [re.sub(r',','.',element) for element in d]
93 | dev = d.pop(0)
94 | if (dev in self.disks) or not self.disks:
95 | dstats[dev] = dict([(k, float(v)) for k, v in zip(hdr, d)])
96 |
97 | return dstats
98 |
99 | def sum_dstats(self, stats, smetrics):
100 | """
101 | Compute the summary statistics for chosen metrics.
102 | """
103 | avg = {}
104 |
105 | for disk, metrics in stats.items():
106 | for mname, metric in metrics.items():
107 | if mname not in smetrics:
108 | continue
109 | if mname in avg:
110 | avg[mname] += metric
111 | else:
112 | avg[mname] = metric
113 |
114 | return avg
115 |
116 | def _run(self, options=None):
117 | """
118 | Run iostat command.
119 | """
120 | close_fds = 'posix' in sys.builtin_module_names
121 | args = '%s %s %s %s %s' % (
122 | self.path,
123 | ''.join(options),
124 | self.interval,
125 | self.count,
126 | ' '.join(self.disks))
127 |
128 | return subprocess.Popen(
129 | args,
130 | bufsize=1,
131 | universal_newlines=True,
132 | shell=True,
133 | stdout=subprocess.PIPE,
134 | close_fds=close_fds)
135 |
136 | @staticmethod
137 | def _get_childs_data(child):
138 | """
139 | Return child's data when avaliable.
140 | """
141 | (stdout, stderr) = child.communicate()
142 | ecode = child.poll()
143 |
144 | if ecode != 0:
145 | raise CmdError('Command %r returned %d' % (child.cmd, ecode))
146 |
147 | return stdout
148 |
149 | def get_diskstats(self):
150 | """
151 | Get all avaliable disks statistics that we can get.
152 | """
153 | options=['-','k','N','d']
154 | extdoptions=['-','k','N','d','x']
155 | if self.no_dm_name:
156 | options.remove('N')
157 | extdoptions.remove('N')
158 | dstats = self._run(options)
159 | extdstats = self._run(extdoptions)
160 | dsd = self._get_childs_data(dstats)
161 | edd = self._get_childs_data(extdstats)
162 | ds = self.parse_diskstats(dsd)
163 | eds = self.parse_diskstats(edd)
164 |
165 | for dk, dv in ds.items():
166 | if dk in eds:
167 | ds[dk].update(eds[dk])
168 |
169 | return ds
170 |
171 |
172 | class IOMon(object):
173 | def __init__(self):
174 | self.plugin_name = 'collectd-iostat-python'
175 | self.iostat_path = '/usr/bin/iostat'
176 | self.interval = 60.0
177 | self.iostat_interval = 2
178 | self.iostat_count = 2
179 | self.iostat_disks = []
180 | self.iostat_nice_names = False
181 | self.iostat_disks_regex = ''
182 | self.iostat_udevnameattr = ''
183 | self.skip_multipath = False
184 | self.verbose_logging = False
185 | self.iostat_no_dm_name = False
186 | self.names = {
187 | 'tps': {'t': 'transfers_per_second'},
188 | 'Blk_read/s': {'t': 'blocks_per_second', 'ti': 'read'},
189 | 'kB_read/s': {'t': 'bytes_per_second', 'ti': 'read', 'm': 1024},
190 | 'MB_read/s': {'t': 'bytes_per_second', 'ti': 'read', 'm': 1048576},
191 | 'Blk_wrtn/s': {'t': 'blocks_per_second', 'ti': 'write'},
192 | 'kB_wrtn/s': {'t': 'bytes_per_second', 'ti': 'write', 'm': 1024},
193 | 'MB_wrtn/s': {'t': 'bytes_per_second', 'ti': 'write', 'm': 1048576},
194 | 'Blk_read': {'t': 'blocks', 'ti': 'read'},
195 | 'kB_read': {'t': 'bytes', 'ti': 'read', 'm': 1024},
196 | 'MB_read': {'t': 'bytes', 'ti': 'read', 'm': 1048576},
197 | 'Blk_wrtn': {'t': 'blocks', 'ti': 'write'},
198 | 'kB_wrtn': {'t': 'bytes', 'ti': 'write', 'm': 1024},
199 | 'MB_wrtn': {'t': 'bytes', 'ti': 'write', 'm': 1048576},
200 | 'rrqm/s': {'t': 'requests_merged_per_second', 'ti': 'read'},
201 | 'wrqm/s': {'t': 'requests_merged_per_second', 'ti': 'write'},
202 | 'r/s': {'t': 'per_second', 'ti': 'read'},
203 | 'w/s': {'t': 'per_second', 'ti': 'write'},
204 | 'rsec/s': {'t': 'sectors_per_second', 'ti': 'read'},
205 | 'rkB/s': {'t': 'bytes_per_second', 'ti': 'read', 'm': 1024},
206 | 'rMB/s': {'t': 'bytes_per_second', 'ti': 'read', 'm': 1048576},
207 | 'wsec/s': {'t': 'sectors_per_second', 'ti': 'write'},
208 | 'wkB/s': {'t': 'bytes_per_second', 'ti': 'write', 'm': 1024},
209 | 'wMB/s': {'t': 'bytes_per_second', 'ti': 'write', 'm': 1048576},
210 | 'avgrq-sz': {'t': 'avg_request_size'},
211 | 'avgqu-sz': {'t': 'avg_request_queue'},
212 | 'await': {'t': 'avg_wait_time'},
213 | 'r_await': {'t': 'avg_wait_time', 'ti': 'read'},
214 | 'w_await': {'t': 'avg_wait_time', 'ti': 'write'},
215 | 'svctm': {'t': 'avg_service_time'},
216 | '%util': {'t': 'percent', 'ti': 'util'}
217 | }
218 |
219 | def log_verbose(self, msg):
220 | if not self.verbose_logging:
221 | return
222 | collectd.info('%s plugin [verbose]: %s' % (self.plugin_name, msg))
223 |
224 | def configure_callback(self, conf):
225 | """
226 | Receive configuration block
227 | """
228 | for node in conf.children:
229 | val = str(node.values[0])
230 |
231 | if node.key == 'Path':
232 | self.iostat_path = val
233 | elif node.key == 'Interval':
234 | self.interval = float(val)
235 | elif node.key == 'IostatInterval':
236 | self.iostat_interval = int(float(val))
237 | elif node.key == 'Count':
238 | self.iostat_count = int(float(val))
239 | elif node.key == 'Disks':
240 | self.iostat_disks = val.split(',')
241 | elif node.key == 'NiceNames':
242 | self.iostat_nice_names = val in ['True', 'true']
243 | elif node.key == 'DisksRegex':
244 | self.iostat_disks_regex = val
245 | elif node.key == 'UdevNameAttr':
246 | self.iostat_udevnameattr = val
247 | elif node.key == 'PluginName':
248 | self.plugin_name = val
249 | elif node.key == 'Verbose':
250 | self.verbose_logging = val in ['True', 'true']
251 | elif node.key == 'SkipPhysicalMultipath':
252 | self.skip_multipath = val in [ 'True', 'true' ]
253 | elif node.key == 'NoDisplayDMName':
254 | self.iostat_no_dm_name = val in [ 'True', 'true' ]
255 | else:
256 | collectd.warning(
257 | '%s plugin: Unknown config key: %s.' % (
258 | self.plugin_name,
259 | node.key))
260 |
261 | self.log_verbose(
262 | 'Configured with iostat=%s, interval=%s, count=%s, disks=%s, '
263 | 'disks_regex=%s udevnameattr=%s skip_multipath=%s no_dm_name=%s' % (
264 | self.iostat_path,
265 | self.iostat_interval,
266 | self.iostat_count,
267 | self.iostat_disks,
268 | self.iostat_disks_regex,
269 | self.iostat_udevnameattr,
270 | self.skip_multipath,
271 | self.iostat_no_dm_name))
272 |
273 | collectd.register_read(self.read_callback, self.interval)
274 |
275 | def dispatch_value(self, plugin_instance, val_type, type_instance, value):
276 | """
277 | Dispatch a value to collectd
278 | """
279 | self.log_verbose(
280 | 'Sending value: %s-%s.%s=%s' % (
281 | self.plugin_name,
282 | plugin_instance,
283 | '-'.join([val_type, type_instance]),
284 | value))
285 |
286 | val = collectd.Values()
287 | val.plugin = self.plugin_name
288 | val.plugin_instance = plugin_instance
289 | val.type = val_type
290 | if len(type_instance):
291 | val.type_instance = type_instance
292 | val.values = [value, ]
293 | val.meta={'0': True}
294 | val.dispatch()
295 |
296 | def read_callback(self):
297 | """
298 | Collectd read callback
299 | """
300 | self.log_verbose('Read callback called')
301 | iostat = IOStat(
302 | path=self.iostat_path,
303 | interval=self.iostat_interval,
304 | count=self.iostat_count,
305 | disks=self.iostat_disks,
306 | no_dm_name=self.iostat_no_dm_name)
307 | ds = iostat.get_diskstats()
308 |
309 | if not ds:
310 | self.log_verbose('%s plugin: No info received.' % self.plugin_name)
311 | return
312 |
313 | if self.iostat_udevnameattr and pyudev_available:
314 | context = pyudev.Context()
315 |
316 | for disk in ds:
317 | if not re.match(self.iostat_disks_regex, disk):
318 | continue
319 | if self.iostat_udevnameattr and pyudev_available:
320 | device = pyudev.Device.from_device_file(context, "/dev/" + disk)
321 | if self.skip_multipath:
322 | mp_managed = device.get('DM_MULTIPATH_DEVICE_PATH')
323 | if mp_managed and mp_managed == '1':
324 | self.log_verbose('Skipping physical multipath disk %s' % disk)
325 | continue
326 | if self.iostat_udevnameattr:
327 | persistent_name = device.get(self.iostat_udevnameattr)
328 | if not persistent_name:
329 | self.log_verbose('Unable to determine disk name based on UdevNameAttr: %s' % self.iostat_udevnameattr)
330 | persistent_name = disk
331 | else:
332 | persistent_name = disk
333 |
334 | for name in ds[disk]:
335 | if self.iostat_nice_names and name in self.names:
336 | val_type = self.names[name]['t']
337 |
338 | if 'ti' in self.names[name]:
339 | type_instance = self.names[name]['ti']
340 | else:
341 | type_instance = ''
342 |
343 | value = ds[disk][name]
344 | if 'm' in self.names[name]:
345 | value *= self.names[name]['m']
346 | else:
347 | val_type = 'gauge'
348 | tbl = maketrans('/-%', '___')
349 | type_instance = name.translate(tbl)
350 | value = ds[disk][name]
351 | self.dispatch_value(
352 | persistent_name, val_type, type_instance, value)
353 |
354 | def restore_sigchld():
355 | """
356 | Restore SIGCHLD handler for python <= v2.6
357 | It will BREAK exec plugin!!!
358 | See https://github.com/deniszh/collectd-iostat-python/issues/2 for details
359 | """
360 | if sys.version_info[0] == 2 and sys.version_info[1] <= 6:
361 | signal.signal(signal.SIGCHLD, signal.SIG_DFL)
362 |
363 |
364 | if __name__ == '__main__':
365 | iostat = IOStat()
366 | ds = iostat.get_diskstats()
367 |
368 | for disk in ds:
369 | for metric in ds[disk]:
370 | tbl = maketrans('/-%', '___')
371 | metric_name = metric.translate(tbl)
372 | print("%s.%s:%s" % (disk, metric_name, ds[disk][metric]))
373 |
374 | sys.exit(0)
375 | else:
376 | import collectd
377 |
378 | iomon = IOMon()
379 |
380 | # Register callbacks
381 | collectd.register_init(restore_sigchld)
382 | collectd.register_config(iomon.configure_callback)
383 |
--------------------------------------------------------------------------------
/iostat_types.db:
--------------------------------------------------------------------------------
1 | avg_request_queue value:GAUGE:0:U
2 | avg_request_size value:GAUGE:0:U
3 | avg_service_time value:GAUGE:0:U
4 | avg_wait_time value:GAUGE:0:U
5 | blocks value:GAUGE:0:U
6 | blocks_per_second value:GAUGE:0:U
7 | bytes_per_second value:GAUGE:0:U
8 | transfers_per_second value:GAUGE:0:U
9 | per_second value:GAUGE:0:U
10 | requests_merged_per_second value:GAUGE:0:U
11 | sectors_per_second value:GAUGE:0:U
12 |
--------------------------------------------------------------------------------