glances_diskio.py 9.5 KB
Newer Older
A
Alessio Sergi 已提交
1 2
# -*- coding: utf-8 -*-
#
3
# This file is part of Glances.
A
Alessio Sergi 已提交
4
#
5
# Copyright (C) 2015 Nicolargo <nicolas@nicolargo.com>
A
Alessio Sergi 已提交
6 7 8 9 10 11 12 13 14 15 16 17 18
#
# Glances is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Glances is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
A
PEP 257  
Alessio Sergi 已提交
19 20

"""Disk I/O plugin."""
A
Alessio Sergi 已提交
21

22 23
import operator

24
from glances.timer import getTimeSinceLastUpdate
A
Alessio Sergi 已提交
25
from glances.plugins.glances_plugin import GlancesPlugin
A
Alessio Sergi 已提交
26

A
flake8  
Alessio Sergi 已提交
27 28 29
import psutil


30 31 32
# Define the history items list
# All items in this list will be historised if the --enable-history tag is set
# 'color' define the graph color in #RGB format
33 34 35 36 37 38 39 40
items_history_list = [{'name': 'read_bytes',
                       'description': 'Bytes read per second',
                       'color': '#00FF00',
                       'y_unit': 'B/s'},
                      {'name': 'write_bytes',
                       'description': 'Bytes write per second',
                       'color': '#FF0000',
                       'y_unit': 'B/s'}]
41

A
Alessio Sergi 已提交
42 43

class Plugin(GlancesPlugin):
A
PEP 257  
Alessio Sergi 已提交
44

A
PEP 257  
Alessio Sergi 已提交
45
    """Glances disks I/O plugin.
A
Alessio Sergi 已提交
46 47 48 49

    stats is a list
    """

50
    def __init__(self, args=None):
A
PEP 257  
Alessio Sergi 已提交
51
        """Init the plugin."""
A
Alessio Sergi 已提交
52
        super(Plugin, self).__init__(args=args, items_history_list=items_history_list)
A
Alessio Sergi 已提交
53 54 55 56

        # We want to display the stat in the curse interface
        self.display_curse = True

57
        # Init the stats
A
Alessio Sergi 已提交
58
        self.reset()
59

60
    def get_key(self):
A
PEP 257  
Alessio Sergi 已提交
61
        """Return the key of the list."""
62 63
        return 'disk_name'

64
    def reset(self):
A
PEP 257  
Alessio Sergi 已提交
65
        """Reset/init the stats."""
66
        self.stats = []
A
Alessio Sergi 已提交
67

68
    @GlancesPlugin._log_result_decorator
69
    def update(self):
A
PEP 257  
Alessio Sergi 已提交
70
        """Update disk I/O stats using the input method."""
71 72 73
        # Reset stats
        self.reset()

74
        if self.input_method == 'local':
75 76 77 78 79 80 81 82
            # Update stats using the standard system lib
            # Grab the stat using the PsUtil disk_io_counters method
            # read_count: number of reads
            # write_count: number of writes
            # read_bytes: number of bytes read
            # write_bytes: number of bytes written
            # read_time: time spent reading from disk (in milliseconds)
            # write_time: time spent writing to disk (in milliseconds)
N
Nicolargo 已提交
83 84
            try:
                diskiocounters = psutil.disk_io_counters(perdisk=True)
A
Alessio Sergi 已提交
85
            except Exception:
N
Nicolargo 已提交
86
                return self.stats
87 88

            # Previous disk IO stats are stored in the diskio_old variable
N
Nicolargo 已提交
89
            if not hasattr(self, 'diskio_old'):
90
                # First call, we init the diskio_old var
A
Alessio Sergi 已提交
91
                try:
92 93 94 95 96 97 98 99 100 101 102
                    self.diskio_old = diskiocounters
                except (IOError, UnboundLocalError):
                    pass
            else:
                # By storing time data we enable Rx/s and Tx/s calculations in the
                # XML/RPC API, which would otherwise be overly difficult work
                # for users of the API
                time_since_update = getTimeSinceLastUpdate('disk')

                diskio_new = diskiocounters
                for disk in diskio_new:
103
                    # By default, RamFS is not displayed (issue #714)
N
Nicolargo 已提交
104
                    if self.args is not None and not self.args.diskio_show_ramfs and disk.startswith('ram'):
105
                        continue
106

107
                    # Do not take hide disk into account
108 109 110
                    if self.is_hide(disk):
                        continue

111
                    # Compute count and bit rate
112
                    try:
113 114 115 116
                        read_count = (diskio_new[disk].read_count -
                                      self.diskio_old[disk].read_count)
                        write_count = (diskio_new[disk].write_count -
                                       self.diskio_old[disk].write_count)
117 118 119 120 121 122 123
                        read_bytes = (diskio_new[disk].read_bytes -
                                      self.diskio_old[disk].read_bytes)
                        write_bytes = (diskio_new[disk].write_bytes -
                                       self.diskio_old[disk].write_bytes)
                        diskstat = {
                            'time_since_update': time_since_update,
                            'disk_name': disk,
124 125
                            'read_count': read_count,
                            'write_count': write_count,
126 127
                            'read_bytes': read_bytes,
                            'write_bytes': write_bytes}
128 129 130
                    except KeyError:
                        continue
                    else:
131
                        diskstat['key'] = self.get_key()
132
                        self.stats.append(diskstat)
A
Alessio Sergi 已提交
133

N
Nicolargo 已提交
134
                # Save stats to compute next bitrate
135
                self.diskio_old = diskio_new
136
        elif self.input_method == 'snmp':
137
            # Update stats using SNMP
138
            # No standard way for the moment...
139
            pass
A
Alessio Sergi 已提交
140

141 142 143
        # Update the history list
        self.update_stats_history('disk_name')

144 145 146
        # Update the view
        self.update_views()

147
        return self.stats
A
Alessio Sergi 已提交
148

149
    def update_views(self):
A
PEP 257  
Alessio Sergi 已提交
150
        """Update stats views."""
151
        # Call the father's method
A
Alessio Sergi 已提交
152
        super(Plugin, self).update_views()
153 154 155

        # Add specifics informations
        # Alert
156
        for i in self.stats:
157 158 159 160 161 162
            disk_real_name = i['disk_name']
            self.views[i[self.get_key()]]['read_bytes']['decoration'] = self.get_alert(int(i['read_bytes'] // i['time_since_update']),
                                                                                       header=disk_real_name + '_rx')
            self.views[i[self.get_key()]]['write_bytes']['decoration'] = self.get_alert(int(i['write_bytes'] // i['time_since_update']),
                                                                                        header=disk_real_name + '_tx')

A
Alessio Sergi 已提交
163
    def msg_curse(self, args=None):
A
PEP 257  
Alessio Sergi 已提交
164
        """Return the dict to display in the curse interface."""
A
Alessio Sergi 已提交
165 166 167
        # Init the return message
        ret = []

168
        # Only process if stats exist and display plugin enable...
D
desbma 已提交
169
        if not self.stats or args.disable_diskio:
170 171
            return ret

A
Alessio Sergi 已提交
172 173
        # Build the string message
        # Header
A
Alessio Sergi 已提交
174
        msg = '{0:9}'.format('DISK I/O')
A
Alessio Sergi 已提交
175
        ret.append(self.curse_add_line(msg, "TITLE"))
176 177 178 179 180 181 182 183 184 185
        if args.diskio_iops:
            msg = '{0:>7}'.format('IOR/s')
            ret.append(self.curse_add_line(msg))
            msg = '{0:>7}'.format('IOW/s')
            ret.append(self.curse_add_line(msg))
        else:
            msg = '{0:>7}'.format('R/s')
            ret.append(self.curse_add_line(msg))
            msg = '{0:>7}'.format('W/s')
            ret.append(self.curse_add_line(msg))
A
Alessio Sergi 已提交
186
        # Disk list (sorted by name)
187
        for i in sorted(self.stats, key=operator.itemgetter(self.get_key())):
188
            # Is there an alias for the disk name ?
N
Nicolargo 已提交
189
            disk_real_name = i['disk_name']
190 191
            disk_name = self.has_alias(i['disk_name'])
            if disk_name is None:
N
Nicolargo 已提交
192
                disk_name = disk_real_name
A
Alessio Sergi 已提交
193 194
            # New line
            ret.append(self.curse_new_line())
195
            if len(disk_name) > 9:
196
                # Cut disk name if it is too long
197
                disk_name = '_' + disk_name[-8:]
A
Alessio Sergi 已提交
198
            msg = '{0:9}'.format(disk_name)
A
Alessio Sergi 已提交
199
            ret.append(self.curse_add_line(msg))
200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231
            if args.diskio_iops:
                # count
                txps = self.auto_unit(
                    int(i['read_count'] // i['time_since_update']))
                rxps = self.auto_unit(
                    int(i['write_count'] // i['time_since_update']))
                msg = '{0:>7}'.format(txps)
                ret.append(self.curse_add_line(msg,
                                               self.get_views(item=i[self.get_key()],
                                                              key='read_count',
                                                              option='decoration')))
                msg = '{0:>7}'.format(rxps)
                ret.append(self.curse_add_line(msg,
                                               self.get_views(item=i[self.get_key()],
                                                              key='write_count',
                                                              option='decoration')))
            else:
                # Bitrate
                txps = self.auto_unit(
                    int(i['read_bytes'] // i['time_since_update']))
                rxps = self.auto_unit(
                    int(i['write_bytes'] // i['time_since_update']))
                msg = '{0:>7}'.format(txps)
                ret.append(self.curse_add_line(msg,
                                               self.get_views(item=i[self.get_key()],
                                                              key='read_bytes',
                                                              option='decoration')))
                msg = '{0:>7}'.format(rxps)
                ret.append(self.curse_add_line(msg,
                                               self.get_views(item=i[self.get_key()],
                                                              key='write_bytes',
                                                              option='decoration')))
A
Alessio Sergi 已提交
232 233

        return ret