Completed
Push — master ( 3dcd25...20576f )
by Nicolas
01:26
created

glances/plugins/glances_diskio.py (1 issue)

1
# -*- coding: utf-8 -*-
2
#
3
# This file is part of Glances.
4
#
5
# Copyright (C) 2015 Nicolargo <[email protected]>
6
#
7
# Glances is free software; you can redistribute it and/or modify
8
# it under the terms of the GNU Lesser General Public License as published by
9
# the Free Software Foundation, either version 3 of the License, or
10
# (at your option) any later version.
11
#
12
# Glances is distributed in the hope that it will be useful,
13
# but WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU Lesser General Public License for more details.
16
#
17
# You should have received a copy of the GNU Lesser General Public License
18
# along with this program. If not, see <http://www.gnu.org/licenses/>.
19
20
"""Disk I/O plugin."""
21
22
import operator
23
24
from glances.timer import getTimeSinceLastUpdate
25
from glances.plugins.glances_plugin import GlancesPlugin
26
27
import psutil
28
29
30
# Define the history items list
31
# All items in this list will be historised if the --enable-history tag is set
32
# 'color' define the graph color in #RGB format
33
items_history_list = [{'name': 'read_bytes',
34
                       'description': 'Bytes read per second',
35
                       'color': '#00FF00',
36
                       'y_unit': 'B/s'},
37
                      {'name': 'write_bytes',
38
                       'description': 'Bytes write per second',
39
                       'color': '#FF0000',
40
                       'y_unit': 'B/s'}]
41
42
43
class Plugin(GlancesPlugin):
44
45
    """Glances disks I/O plugin.
46
47
    stats is a list
48
    """
49
50
    def __init__(self, args=None):
51
        """Init the plugin."""
52
        super(Plugin, self).__init__(args=args, items_history_list=items_history_list)
53
54
        # We want to display the stat in the curse interface
55
        self.display_curse = True
56
57
        # Init the stats
58
        self.reset()
59
60
    def get_key(self):
61
        """Return the key of the list."""
62
        return 'disk_name'
63
64
    def reset(self):
65
        """Reset/init the stats."""
66
        self.stats = []
67
68
    @GlancesPlugin._log_result_decorator
69
    def update(self):
70
        """Update disk I/O stats using the input method."""
71
        # Reset stats
72
        self.reset()
73
74
        if self.input_method == 'local':
75
            # Update stats using the standard system lib
76
            # Grab the stat using the PsUtil disk_io_counters method
77
            # read_count: number of reads
78
            # write_count: number of writes
79
            # read_bytes: number of bytes read
80
            # write_bytes: number of bytes written
81
            # read_time: time spent reading from disk (in milliseconds)
82
            # write_time: time spent writing to disk (in milliseconds)
83
            try:
84
                diskiocounters = psutil.disk_io_counters(perdisk=True)
85
            except Exception:
86
                return self.stats
87
88
            # Previous disk IO stats are stored in the diskio_old variable
89
            if not hasattr(self, 'diskio_old'):
90
                # First call, we init the diskio_old var
91
                try:
92
                    self.diskio_old = diskiocounters
93
                except (IOError, UnboundLocalError):
94
                    pass
95
            else:
96
                # By storing time data we enable Rx/s and Tx/s calculations in the
97
                # XML/RPC API, which would otherwise be overly difficult work
98
                # for users of the API
99
                time_since_update = getTimeSinceLastUpdate('disk')
100
101
                diskio_new = diskiocounters
102
                for disk in diskio_new:
103
                    # By default, RamFS is not displayed (issue #714)
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
                    if self.is_hide(disk):
109
                        continue
110
111
                    # Compute count and bit rate
112
                    try:
113
                        read_count = (diskio_new[disk].read_count -
114
                                      self.diskio_old[disk].read_count)
115
                        write_count = (diskio_new[disk].write_count -
116
                                       self.diskio_old[disk].write_count)
117
                        read_bytes = (diskio_new[disk].read_bytes -
118
                                      self.diskio_old[disk].read_bytes)
119
                        write_bytes = (diskio_new[disk].write_bytes -
120
                                       self.diskio_old[disk].write_bytes)
121
                        diskstat = {
122
                            'time_since_update': time_since_update,
123
                            'disk_name': disk,
124
                            'read_count': read_count,
125
                            'write_count': write_count,
126
                            'read_bytes': read_bytes,
127
                            'write_bytes': write_bytes}
128
                        # Add alias if exist (define in the configuration file)
129
                        if self.has_alias(disk) is not None:
130
                            diskstat['alias'] = self.has_alias(disk)
131
                    except KeyError:
132
                        continue
133
                    else:
134
                        diskstat['key'] = self.get_key()
135
                        self.stats.append(diskstat)
136
137
                # Save stats to compute next bitrate
138
                self.diskio_old = diskio_new
139
        elif self.input_method == 'snmp':
140
            # Update stats using SNMP
141
            # No standard way for the moment...
142
            pass
143 View Code Duplication
0 ignored issues
show
This code seems to be duplicated in your project.
Loading history...
144
        # Update the history list
145
        self.update_stats_history('disk_name')
146
147
        # Update the view
148
        self.update_views()
149
150
        return self.stats
151
152
    def update_views(self):
153
        """Update stats views."""
154
        # Call the father's method
155
        super(Plugin, self).update_views()
156
157
        # Add specifics informations
158
        # Alert
159
        for i in self.stats:
160
            disk_real_name = i['disk_name']
161
            self.views[i[self.get_key()]]['read_bytes']['decoration'] = self.get_alert(int(i['read_bytes'] // i['time_since_update']),
162
                                                                                       header=disk_real_name + '_rx')
163
            self.views[i[self.get_key()]]['write_bytes']['decoration'] = self.get_alert(int(i['write_bytes'] // i['time_since_update']),
164
                                                                                        header=disk_real_name + '_tx')
165
166
    def msg_curse(self, args=None):
167
        """Return the dict to display in the curse interface."""
168
        # Init the return message
169
        ret = []
170
171
        # Only process if stats exist and display plugin enable...
172
        if not self.stats or args.disable_diskio:
173
            return ret
174
175
        # Build the string message
176
        # Header
177
        msg = '{:9}'.format('DISK I/O')
178
        ret.append(self.curse_add_line(msg, "TITLE"))
179
        if args.diskio_iops:
180
            msg = '{:>7}'.format('IOR/s')
181
            ret.append(self.curse_add_line(msg))
182
            msg = '{:>7}'.format('IOW/s')
183
            ret.append(self.curse_add_line(msg))
184
        else:
185
            msg = '{:>7}'.format('R/s')
186
            ret.append(self.curse_add_line(msg))
187
            msg = '{:>7}'.format('W/s')
188
            ret.append(self.curse_add_line(msg))
189
        # Disk list (sorted by name)
190
        for i in sorted(self.stats, key=operator.itemgetter(self.get_key())):
191
            # Is there an alias for the disk name ?
192
            disk_real_name = i['disk_name']
193
            disk_name = self.has_alias(i['disk_name'])
194
            if disk_name is None:
195
                disk_name = disk_real_name
196
            # New line
197
            ret.append(self.curse_new_line())
198
            if len(disk_name) > 9:
199
                # Cut disk name if it is too long
200
                disk_name = '_' + disk_name[-8:]
201
            msg = '{:9}'.format(disk_name)
202
            ret.append(self.curse_add_line(msg))
203
            if args.diskio_iops:
204
                # count
205
                txps = self.auto_unit(
206
                    int(i['read_count'] // i['time_since_update']))
207
                rxps = self.auto_unit(
208
                    int(i['write_count'] // i['time_since_update']))
209
                msg = '{:>7}'.format(txps)
210
                ret.append(self.curse_add_line(msg,
211
                                               self.get_views(item=i[self.get_key()],
212
                                                              key='read_count',
213
                                                              option='decoration')))
214
                msg = '{:>7}'.format(rxps)
215
                ret.append(self.curse_add_line(msg,
216
                                               self.get_views(item=i[self.get_key()],
217
                                                              key='write_count',
218
                                                              option='decoration')))
219
            else:
220
                # Bitrate
221
                txps = self.auto_unit(
222
                    int(i['read_bytes'] // i['time_since_update']))
223
                rxps = self.auto_unit(
224
                    int(i['write_bytes'] // i['time_since_update']))
225
                msg = '{:>7}'.format(txps)
226
                ret.append(self.curse_add_line(msg,
227
                                               self.get_views(item=i[self.get_key()],
228
                                                              key='read_bytes',
229
                                                              option='decoration')))
230
                msg = '{:>7}'.format(rxps)
231
                ret.append(self.curse_add_line(msg,
232
                                               self.get_views(item=i[self.get_key()],
233
                                                              key='write_bytes',
234
                                                              option='decoration')))
235
236
        return ret
237