Issues (49)

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

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