Issues (48)

glances/plugins/diskio/__init__.py (1 issue)

1
# -*- coding: utf-8 -*-
2
#
3
# This file is part of Glances.
4
#
5
# SPDX-FileCopyrightText: 2024 Nicolas Hennion <[email protected]>
6
#
7
# SPDX-License-Identifier: LGPL-3.0-only
8
#
9
10
"""Disk I/O plugin."""
11
from __future__ import unicode_literals
12
13
from glances.logger import logger
14
from glances.globals import nativestr
15
from glances.plugins.plugin.model import GlancesPluginModel
16
17
import psutil
18
19
# Fields description
20
# description: human readable description
21
# short_name: shortname to use un UI
22
# unit: unit type
23
# rate: if True then compute and add *_gauge and *_rate_per_is fields
24
# min_symbol: Auto unit should be used if value > than 1 'X' (K, M, G)...
25
fields_description = {
26
    'disk_name': {'description': 'Disk name.'},
27
    'read_count': {
28
        'description': 'Number of reads.',
29
        'rate': True,
30
        'unit': 'number',
31
    },
32
    'write_count': {
33
        'description': 'Number of writes.',
34
        'rate': True,
35
        'unit': 'number',
36
    },
37
    'read_bytes': {
38
        'description': 'Number of bytes read.',
39
        'rate': True,
40
        'unit': 'byte',
41
    },
42
    'write_bytes': {
43
        'description': 'Number of bytes written.',
44
        'rate': True,
45
        'unit': 'byte',
46
    },
47
}
48
49
# Define the history items list
50
items_history_list = [
51
    {'name': 'read_bytes_rate_per_sec', 'description': 'Bytes read per second', 'y_unit': 'B/s'},
52
    {'name': 'write_bytes_rate_per_sec', 'description': 'Bytes write per second', 'y_unit': 'B/s'},
53
]
54
55
56
class PluginModel(GlancesPluginModel):
57
    """Glances disks I/O plugin.
58
59
    stats is a list
60
    """
61
62 View Code Duplication
    def __init__(self, args=None, config=None):
0 ignored issues
show
This code seems to be duplicated in your project.
Loading history...
63
        """Init the plugin."""
64
        super(PluginModel, self).__init__(
65
            args=args,
66
            config=config,
67
            items_history_list=items_history_list,
68
            stats_init_value=[],
69
            fields_description=fields_description,
70
        )
71
72
        # We want to display the stat in the curse interface
73
        self.display_curse = True
74
75
        # Hide stats if it has never been != 0
76
        if config is not None:
77
            self.hide_zero = config.get_bool_value(self.plugin_name, 'hide_zero', default=False)
78
        else:
79
            self.hide_zero = False
80
        self.hide_zero_fields = ['read_bytes', 'write_bytes']
81
82
        # Force a first update because we need two updates to have the first stat
83
        self.update()
84
        self.refresh_timer.set(0)
85
86
    def get_key(self):
87
        """Return the key of the list."""
88
        return 'disk_name'
89
90
    @GlancesPluginModel._check_decorator
91
    @GlancesPluginModel._log_result_decorator
92
    def update(self):
93
        """Update disk I/O stats using the input method."""
94
        # Update the stats
95
        if self.input_method == 'local':
96
            stats = self.update_local()
97
        else:
98
            stats = self.get_init_value()
99
100
        # Update the stats
101
        self.stats = stats
102
103
        return self.stats
104
105
    @GlancesPluginModel._manage_rate
106
    def update_local(self):
107
        stats = self.get_init_value()
108
109
        try:
110
            diskio = psutil.disk_io_counters(perdisk=True)
111
        except Exception:
112
            return stats
113
114
        for disk_name, disk_stat in diskio.items():
115
            # By default, RamFS is not displayed (issue #714)
116
            if self.args is not None and not self.args.diskio_show_ramfs and disk_name.startswith('ram'):
117
                continue
118
119
            # Shall we display the stats ?
120
            if not self.is_display(disk_name):
121
                continue
122
123
            # Filter stats to keep only the fields we want (define in fields_description)
124
            # It will also convert psutil objects to a standard Python dict
125
            stat = self.filter_stats(disk_stat)
126
127
            # Add the key
128
            stat['key'] = self.get_key()
129
130
            # Add disk name
131
            stat['disk_name'] = disk_name
132
133
            # Add alias if exist (define in the configuration file)
134
            if self.has_alias(disk_name) is not None:
135
                stat['alias'] = self.has_alias(disk_name)
136
137
            stats.append(stat)
138
139
        return stats
140
141
    def update_views(self):
142
        """Update stats views."""
143
        # Call the father's method
144
        super(PluginModel, self).update_views()
145
146
        # Check if the stats should be hidden
147
        self.update_views_hidden()
148
149
        # Add specifics information
150
        # Alert
151
        for i in self.get_raw():
152
            disk_real_name = i['disk_name']
153
            self.views[i[self.get_key()]]['read_bytes']['decoration'] = self.get_alert(
154
                i['read_bytes'], header=disk_real_name + '_rx'
155
            )
156
            self.views[i[self.get_key()]]['write_bytes']['decoration'] = self.get_alert(
157
                i['write_bytes'], header=disk_real_name + '_tx'
158
            )
159
160
    def msg_curse(self, args=None, max_width=None):
161
        """Return the dict to display in the curse interface."""
162
        # Init the return message
163
        ret = []
164
165
        # Only process if stats exist and display plugin enable...
166
        if not self.stats or self.is_disabled():
167
            return ret
168
169
        # Max size for the interface name
170
        if max_width:
171
            name_max_width = max_width - 13
172
        else:
173
            # No max_width defined, return an emptu curse message
174
            logger.debug("No max_width defined for the {} plugin, it will not be displayed.".format(self.plugin_name))
175
            return ret
176
177
        # Header
178
        msg = '{:{width}}'.format('DISK I/O', width=name_max_width)
179
        ret.append(self.curse_add_line(msg, "TITLE"))
180
        if args.diskio_iops:
181
            msg = '{:>8}'.format('IOR/s')
182
            ret.append(self.curse_add_line(msg))
183
            msg = '{:>7}'.format('IOW/s')
184
            ret.append(self.curse_add_line(msg))
185
        else:
186
            msg = '{:>8}'.format('R/s')
187
            ret.append(self.curse_add_line(msg))
188
            msg = '{:>7}'.format('W/s')
189
            ret.append(self.curse_add_line(msg))
190
        # Disk list (sorted by name)
191
        for i in self.sorted_stats():
192
            # Hide stats if never be different from 0 (issue #1787)
193
            if all([self.get_views(item=i[self.get_key()], key=f, option='hidden') for f in self.hide_zero_fields]):
194
                continue
195
            # Is there an alias for the disk name ?
196
            disk_name = i['alias'] if 'alias' in i else i['disk_name']
197
            # New line
198
            ret.append(self.curse_new_line())
199
            if len(disk_name) > name_max_width:
200
                # Cut disk name if it is too long
201
                disk_name = disk_name[:name_max_width] + '_'
202
            msg = '{:{width}}'.format(nativestr(disk_name), width=name_max_width + 1)
203
            ret.append(self.curse_add_line(msg))
204
            if args.diskio_iops:
205
                # count
206
                txps = self.auto_unit(i.get('read_count_rate_per_sec', None))
207
                rxps = self.auto_unit(i.get('write_count_rate_per_sec', None))
208
                msg = '{:>7}'.format(txps)
209
                ret.append(
210
                    self.curse_add_line(
211
                        msg, self.get_views(item=i[self.get_key()], key='read_count', option='decoration')
212
                    )
213
                )
214
                msg = '{:>7}'.format(rxps)
215
                ret.append(
216
                    self.curse_add_line(
217
                        msg, self.get_views(item=i[self.get_key()], key='write_count', option='decoration')
218
                    )
219
                )
220
            else:
221
                # Bitrate
222
                txps = self.auto_unit(i.get('read_bytes_rate_per_sec', None))
223
                rxps = self.auto_unit(i.get('write_bytes_rate_per_sec', None))
224
                msg = '{:>7}'.format(txps)
225
                ret.append(
226
                    self.curse_add_line(
227
                        msg, self.get_views(item=i[self.get_key()], key='read_bytes', option='decoration')
228
                    )
229
                )
230
                msg = '{:>7}'.format(rxps)
231
                ret.append(
232
                    self.curse_add_line(
233
                        msg, self.get_views(item=i[self.get_key()], key='write_bytes', option='decoration')
234
                    )
235
                )
236
237
        return ret
238