glances_diskio.py 9.3 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
N
Nicolargo 已提交
33 34
items_history_list = [{'name': 'read_bytes', 'color': '#00FF00', 'y_unit': 'B/s'},
                      {'name': 'write_bytes', 'color': '#FF0000', 'y_unit': 'B/s'}]
35

A
Alessio Sergi 已提交
36 37

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

A
PEP 257  
Alessio Sergi 已提交
39
    """Glances disks I/O plugin.
A
Alessio Sergi 已提交
40 41 42 43

    stats is a list
    """

44
    def __init__(self, args=None):
A
PEP 257  
Alessio Sergi 已提交
45
        """Init the plugin."""
A
Alessio Sergi 已提交
46
        super(Plugin, self).__init__(args=args, items_history_list=items_history_list)
A
Alessio Sergi 已提交
47 48 49 50

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

51
        # Init the stats
A
Alessio Sergi 已提交
52
        self.reset()
53

54
    def get_key(self):
A
PEP 257  
Alessio Sergi 已提交
55
        """Return the key of the list."""
56 57
        return 'disk_name'

58
    def reset(self):
A
PEP 257  
Alessio Sergi 已提交
59
        """Reset/init the stats."""
60
        self.stats = []
A
Alessio Sergi 已提交
61

62
    @GlancesPlugin._log_result_decorator
63
    def update(self):
A
PEP 257  
Alessio Sergi 已提交
64
        """Update disk I/O stats using the input method."""
65 66 67
        # Reset stats
        self.reset()

68
        if self.input_method == 'local':
69 70 71 72 73 74 75 76
            # 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 已提交
77 78
            try:
                diskiocounters = psutil.disk_io_counters(perdisk=True)
A
Alessio Sergi 已提交
79
            except Exception:
N
Nicolargo 已提交
80
                return self.stats
81 82

            # Previous disk IO stats are stored in the diskio_old variable
N
Nicolargo 已提交
83
            if not hasattr(self, 'diskio_old'):
84
                # First call, we init the diskio_old var
A
Alessio Sergi 已提交
85
                try:
86 87 88 89 90 91 92 93 94 95 96
                    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:
97
                    # By default, RamFS is not displayed (issue #714)
N
Nicolargo 已提交
98
                    if self.args is not None and not self.args.diskio_show_ramfs and disk.startswith('ram'):
99
                        continue
100

101
                    # Do not take hide disk into account
102 103 104
                    if self.is_hide(disk):
                        continue

105
                    # Compute count and bit rate
106
                    try:
107 108 109 110
                        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)
111 112 113 114 115 116 117
                        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,
118 119
                            'read_count': read_count,
                            'write_count': write_count,
120 121
                            'read_bytes': read_bytes,
                            'write_bytes': write_bytes}
122 123 124
                    except KeyError:
                        continue
                    else:
125
                        diskstat['key'] = self.get_key()
126
                        self.stats.append(diskstat)
A
Alessio Sergi 已提交
127

N
Nicolargo 已提交
128
                # Save stats to compute next bitrate
129
                self.diskio_old = diskio_new
130
        elif self.input_method == 'snmp':
131
            # Update stats using SNMP
132
            # No standard way for the moment...
133
            pass
A
Alessio Sergi 已提交
134

135 136 137
        # Update the history list
        self.update_stats_history('disk_name')

138 139 140
        # Update the view
        self.update_views()

141
        return self.stats
A
Alessio Sergi 已提交
142

143
    def update_views(self):
A
PEP 257  
Alessio Sergi 已提交
144
        """Update stats views."""
145
        # Call the father's method
A
Alessio Sergi 已提交
146
        super(Plugin, self).update_views()
147 148 149

        # Add specifics informations
        # Alert
150
        for i in self.stats:
151 152 153 154 155 156
            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 已提交
157
    def msg_curse(self, args=None):
A
PEP 257  
Alessio Sergi 已提交
158
        """Return the dict to display in the curse interface."""
A
Alessio Sergi 已提交
159 160 161
        # Init the return message
        ret = []

162
        # Only process if stats exist and display plugin enable...
D
desbma 已提交
163
        if not self.stats or args.disable_diskio:
164 165
            return ret

A
Alessio Sergi 已提交
166 167
        # Build the string message
        # Header
A
Alessio Sergi 已提交
168
        msg = '{0:9}'.format('DISK I/O')
A
Alessio Sergi 已提交
169
        ret.append(self.curse_add_line(msg, "TITLE"))
170 171 172 173 174 175 176 177 178 179
        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 已提交
180
        # Disk list (sorted by name)
181
        for i in sorted(self.stats, key=operator.itemgetter(self.get_key())):
182
            # Is there an alias for the disk name ?
N
Nicolargo 已提交
183
            disk_real_name = i['disk_name']
184 185
            disk_name = self.has_alias(i['disk_name'])
            if disk_name is None:
N
Nicolargo 已提交
186
                disk_name = disk_real_name
A
Alessio Sergi 已提交
187 188
            # New line
            ret.append(self.curse_new_line())
189
            if len(disk_name) > 9:
190
                # Cut disk name if it is too long
191
                disk_name = '_' + disk_name[-8:]
A
Alessio Sergi 已提交
192
            msg = '{0:9}'.format(disk_name)
A
Alessio Sergi 已提交
193
            ret.append(self.curse_add_line(msg))
194 195 196 197 198 199 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
            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 已提交
226 227

        return ret