glances_diskio.py 7.6 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 network_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 98 99 100
                    # By default, RamFS is not displayed (issue #714)
                    if not self.args.diskio_show_ramfs and disk.startswith('ram'):
                        continue
                    # Compute bitrate
101
                    try:
102 103 104 105 106 107 108 109 110
                        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,
                            'read_bytes': read_bytes,
                            'write_bytes': write_bytes}
111 112 113
                    except KeyError:
                        continue
                    else:
114
                        diskstat['key'] = self.get_key()
115
                        self.stats.append(diskstat)
A
Alessio Sergi 已提交
116

N
Nicolargo 已提交
117
                # Save stats to compute next bitrate
118
                self.diskio_old = diskio_new
119
        elif self.input_method == 'snmp':
120
            # Update stats using SNMP
121
            # No standard way for the moment...
122
            pass
A
Alessio Sergi 已提交
123

124 125 126
        # Update the history list
        self.update_stats_history('disk_name')

127 128 129
        # Update the view
        self.update_views()

130
        return self.stats
A
Alessio Sergi 已提交
131

132
    def update_views(self):
A
PEP 257  
Alessio Sergi 已提交
133
        """Update stats views."""
134
        # Call the father's method
A
Alessio Sergi 已提交
135
        super(Plugin, self).update_views()
136 137 138

        # Add specifics informations
        # Alert
139
        for i in self.stats:
140 141 142 143 144 145
            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 已提交
146
    def msg_curse(self, args=None):
A
PEP 257  
Alessio Sergi 已提交
147
        """Return the dict to display in the curse interface."""
A
Alessio Sergi 已提交
148 149 150
        # Init the return message
        ret = []

151
        # Only process if stats exist and display plugin enable...
D
desbma 已提交
152
        if not self.stats or args.disable_diskio:
153 154
            return ret

A
Alessio Sergi 已提交
155 156
        # Build the string message
        # Header
A
Alessio Sergi 已提交
157
        msg = '{0:9}'.format('DISK I/O')
A
Alessio Sergi 已提交
158
        ret.append(self.curse_add_line(msg, "TITLE"))
A
Alessio Sergi 已提交
159
        msg = '{0:>7}'.format('R/s')
A
Alessio Sergi 已提交
160
        ret.append(self.curse_add_line(msg))
A
Alessio Sergi 已提交
161
        msg = '{0:>7}'.format('W/s')
A
Alessio Sergi 已提交
162 163
        ret.append(self.curse_add_line(msg))
        # Disk list (sorted by name)
164
        for i in sorted(self.stats, key=operator.itemgetter(self.get_key())):
165
            # Do not display hidden interfaces
166
            if self.is_hide(i['disk_name']):
167
                continue
168
            # Is there an alias for the disk name ?
N
Nicolargo 已提交
169
            disk_real_name = i['disk_name']
170 171
            disk_name = self.has_alias(i['disk_name'])
            if disk_name is None:
N
Nicolargo 已提交
172
                disk_name = disk_real_name
A
Alessio Sergi 已提交
173 174
            # New line
            ret.append(self.curse_new_line())
175
            if len(disk_name) > 9:
176
                # Cut disk name if it is too long
177
                disk_name = '_' + disk_name[-8:]
A
Alessio Sergi 已提交
178
            msg = '{0:9}'.format(disk_name)
A
Alessio Sergi 已提交
179
            ret.append(self.curse_add_line(msg))
N
Nicolargo 已提交
180 181 182 183
            txps = self.auto_unit(
                int(i['read_bytes'] // i['time_since_update']))
            rxps = self.auto_unit(
                int(i['write_bytes'] // i['time_since_update']))
A
Alessio Sergi 已提交
184
            msg = '{0:>7}'.format(txps)
N
Nicolargo 已提交
185
            ret.append(self.curse_add_line(msg,
186 187 188
                                           self.get_views(item=i[self.get_key()],
                                                          key='read_bytes',
                                                          option='decoration')))
A
Alessio Sergi 已提交
189
            msg = '{0:>7}'.format(rxps)
N
Nicolargo 已提交
190
            ret.append(self.curse_add_line(msg,
191 192 193
                                           self.get_views(item=i[self.get_key()],
                                                          key='write_bytes',
                                                          option='decoration')))
A
Alessio Sergi 已提交
194 195

        return ret