glances_diskio.py 7.4 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 25
import psutil

26
# Import Glances libs
A
Alessio Sergi 已提交
27
from glances.core.glances_timer import getTimeSinceLastUpdate
A
Alessio Sergi 已提交
28
from glances.plugins.glances_plugin import GlancesPlugin
A
Alessio Sergi 已提交
29

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 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."""
N
Nicolargo 已提交
46 47
        GlancesPlugin.__init__(
            self, args=args, items_history_list=items_history_list)
A
Alessio Sergi 已提交
48 49 50 51

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

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

55 56 57 58
    def get_key(self):
        """Return the key of the list"""
        return 'disk_name'

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

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

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

            # Previous disk IO stats are stored in the diskio_old variable
N
Nicolargo 已提交
84
            if not hasattr(self, 'diskio_old'):
85
                # First call, we init the network_old var
A
Alessio Sergi 已提交
86
                try:
87 88 89 90 91 92 93 94 95 96 97 98
                    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:
                    try:
99 100 101 102 103 104 105 106 107
                        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}
108 109 110
                    except KeyError:
                        continue
                    else:
111
                        diskstat['key'] = self.get_key()
112
                        self.stats.append(diskstat)
A
Alessio Sergi 已提交
113

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

121 122 123
        # Update the history list
        self.update_stats_history('disk_name')

124 125 126
        # Update the view
        self.update_views()

127
        return self.stats
A
Alessio Sergi 已提交
128

129 130 131 132 133 134 135
    def update_views(self):
        """Update stats views"""
        # Call the father's method
        GlancesPlugin.update_views(self)

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

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

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

        return ret