Test Failed
Push — develop ( 66c9ff...e21229 )
by Nicolas
05:06
created

glances/plugins/glances_plugin.py (1 issue)

1
# -*- coding: utf-8 -*-
2
#
3
# This file is part of Glances.
4
#
5
# Copyright (C) 2019 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
"""
21
I am your father...
22
23
...for all Glances plugins.
24
"""
25
26
import re
27
import json
28
import copy
29
from operator import itemgetter
30
31
from glances.compat import iterkeys, itervalues, listkeys, map, mean, nativestr
32
from glances.actions import GlancesActions
33
from glances.history import GlancesHistory
34
from glances.logger import logger
35
from glances.events import glances_events
36
from glances.thresholds import glances_thresholds
37
38
39
class GlancesPlugin(object):
40
    """Main class for Glances plugin."""
41
42
    def __init__(self,
43
                 args=None,
44
                 config=None,
45
                 items_history_list=None,
46
                 stats_init_value={}):
47
        """Init the plugin of plugins class.
48
49
        All Glances' plugins should inherit from this class. Most of the
50
        methods are already implemented in the father classes.
51
52
        Your plugin should return a dict or a list of dicts (stored in the
53
        self.stats). As an example, you can have a look on the mem plugin
54
        (for dict) or network (for list of dicts).
55
56
        A plugin should implement:
57
        - the __init__ constructor: define the self.display_curse
58
        - the reset method: to set your self.stats variable to {} or []
59
        - the update method: where your self.stats variable is set
60
        and optionnaly:
61
        - the get_key method: set the key of the dict (only for list of dict)
62
        - the update_view method: only if you need to trick your output
63
        - the msg_curse: define the curse (UI) message (if display_curse is True)
64
65
        :args: args parameters
66
        :items_history_list: list of items to store in the history
67
        :stats_init_value: Default value for a stats item
68
        """
69
        # Plugin name (= module name without glances_)
70
        pos = self.__class__.__module__.find('glances_') + len('glances') + 1
71
        self.plugin_name = self.__class__.__module__[pos:]
72
        # logger.debug("Init plugin %s" % self.plugin_name)
73
74
        # Init the args
75
        self.args = args
76
77
        # Init the default alignement (for curses)
78
        self._align = 'left'
79
80
        # Init the input method
81
        self._input_method = 'local'
82
        self._short_system_name = None
83
84
        # Init the history list
85
        self.items_history_list = items_history_list
86
        self.stats_history = self.init_stats_history()
87
88
        # Init the limits (configuration keys) dictionnary
89
        self._limits = dict()
90
        if not self.load_limits(config=config):
91
            logger.debug('Can not load section {} in {}'.format(self.plugin_name,
92
                                                                config))
93
94
        # Init the actions
95
        self.actions = GlancesActions(args=args)
96
97
        # Init the views
98
        self.views = dict()
99
100
        # Init the stats
101
        self.stats_init_value = stats_init_value
102
        self.stats = None
103
        self.reset()
104
105
    def __repr__(self):
106
        """Return the raw stats."""
107
        return self.stats
108
109
    def __str__(self):
110
        """Return the human-readable stats."""
111
        return str(self.stats)
112
113
    def get_init_value(self):
114
        """Return a copy of the init value."""
115
        return copy.copy(self.stats_init_value)
116
117
    def reset(self):
118
        """Reset the stats.
119
120
        This method should be overwrited by childs' classes.
121
        """
122
        self.stats = self.get_init_value()
123
124
    def exit(self):
125
        """Just log an event when Glances exit."""
126
        logger.debug("Stop the {} plugin".format(self.plugin_name))
127
128
    def get_key(self):
129
        """Return the key of the list."""
130
        return None
131
132
    def is_enable(self, plugin_name=None):
133
        """Return true if plugin is enabled."""
134
        if not plugin_name:
135
            plugin_name = self.plugin_name
136
        try:
137
            d = getattr(self.args, 'disable_' + plugin_name)
138
        except AttributeError:
139
            return True
140
        else:
141
            return d is False
142
143
    def is_disable(self, plugin_name=None):
144
        """Return true if plugin is disabled."""
145
        return not self.is_enable(plugin_name=plugin_name)
146
147
    def _json_dumps(self, d):
148
        """Return the object 'd' in a JSON format.
149
150
        Manage the issue #815 for Windows OS
151
        """
152
        try:
153
            return json.dumps(d)
154
        except UnicodeDecodeError:
155
            return json.dumps(d, ensure_ascii=False)
156
157
    def history_enable(self):
158
        return self.args is not None and not self.args.disable_history and self.get_items_history_list() is not None
159
160
    def init_stats_history(self):
161
        """Init the stats history (dict of GlancesAttribute)."""
162
        if self.history_enable():
163
            init_list = [a['name'] for a in self.get_items_history_list()]
164
            logger.debug("Stats history activated for plugin {} (items: {})".format(self.plugin_name, init_list))
165
        return GlancesHistory()
166
167
    def reset_stats_history(self):
168
        """Reset the stats history (dict of GlancesAttribute)."""
169
        if self.history_enable():
170
            reset_list = [a['name'] for a in self.get_items_history_list()]
171
            logger.debug("Reset history for plugin {} (items: {})".format(self.plugin_name, reset_list))
172
            self.stats_history.reset()
173
174
    def update_stats_history(self):
175
        """Update stats history."""
176
        # If the plugin data is a dict, the dict's key should be used
177
        if self.get_key() is None:
178
            item_name = ''
179
        else:
180
            item_name = self.get_key()
181
        # Build the history
182
        if self.get_export() and self.history_enable():
183
            for i in self.get_items_history_list():
184
                if isinstance(self.get_export(), list):
185
                    # Stats is a list of data
186
                    # Iter throught it (for exemple, iter throught network
187
                    # interface)
188
                    for l in self.get_export():
189
                        self.stats_history.add(
190
                            nativestr(l[item_name]) + '_' + nativestr(i['name']),
191
                            l[i['name']],
192
                            description=i['description'],
193
                            history_max_size=self._limits['history_size'])
194
                else:
195
                    # Stats is not a list
196
                    # Add the item to the history directly
197
                    self.stats_history.add(nativestr(i['name']),
198
                                           self.get_export()[i['name']],
199
                                           description=i['description'],
200
                                           history_max_size=self._limits['history_size'])
201
202
    def get_items_history_list(self):
203
        """Return the items history list."""
204
        return self.items_history_list
205
206
    def get_raw_history(self, item=None, nb=0):
207
        """Return the history (RAW format).
208
209
        - the stats history (dict of list) if item is None
210
        - the stats history for the given item (list) instead
211
        - None if item did not exist in the history
212
        """
213
        s = self.stats_history.get(nb=nb)
214
        if item is None:
215
            return s
216
        else:
217
            if item in s:
218
                return s[item]
219
            else:
220
                return None
221
222
    def get_json_history(self, item=None, nb=0):
223
        """Return the history (JSON format).
224
225
        - the stats history (dict of list) if item is None
226
        - the stats history for the given item (list) instead
227
        - None if item did not exist in the history
228
        Limit to lasts nb items (all if nb=0)
229
        """
230
        s = self.stats_history.get_json(nb=nb)
231
        if item is None:
232
            return s
233
        else:
234
            if item in s:
235
                return s[item]
236
            else:
237
                return None
238
239
    def get_export_history(self, item=None):
240
        """Return the stats history object to export."""
241
        return self.get_raw_history(item=item)
242
243
    def get_stats_history(self, item=None, nb=0):
244
        """Return the stats history (JSON format)."""
245
        s = self.get_json_history(nb=nb)
246
247
        if item is None:
248
            return self._json_dumps(s)
249
250 View Code Duplication
        if isinstance(s, dict):
0 ignored issues
show
This code seems to be duplicated in your project.
Loading history...
251
            try:
252
                return self._json_dumps({item: s[item]})
253
            except KeyError as e:
254
                logger.error("Cannot get item history {} ({})".format(item, e))
255
                return None
256
        elif isinstance(s, list):
257
            try:
258
                # Source:
259
                # http://stackoverflow.com/questions/4573875/python-get-index-of-dictionary-item-in-list
260
                return self._json_dumps({item: map(itemgetter(item), s)})
261
            except (KeyError, ValueError) as e:
262
                logger.error("Cannot get item history {} ({})".format(item, e))
263
                return None
264
        else:
265
            return None
266
267
    def get_trend(self, item, nb=6):
268
        """Get the trend regarding to the last nb values.
269
270
        The trend is the diff between the mean of the last nb values
271
        and the current one.
272
        """
273
        raw_history = self.get_raw_history(item=item, nb=nb)
274
        if raw_history is None or len(raw_history) < nb:
275
            return None
276
        last_nb = [v[1] for v in raw_history]
277
        return last_nb[-1] - mean(last_nb[:-1])
278
279
    @property
280
    def input_method(self):
281
        """Get the input method."""
282
        return self._input_method
283
284
    @input_method.setter
285
    def input_method(self, input_method):
286
        """Set the input method.
287
288
        * local: system local grab (psutil or direct access)
289
        * snmp: Client server mode via SNMP
290
        * glances: Client server mode via Glances API
291
        """
292
        self._input_method = input_method
293
294
    @property
295
    def short_system_name(self):
296
        """Get the short detected OS name (SNMP)."""
297
        return self._short_system_name
298
299
    def sorted_stats(self):
300
        """Get the stats sorted by an alias (if present) or key."""
301
        key = self.get_key()
302
        return sorted(self.stats, key=lambda stat: tuple(map(
303
            lambda part: int(part) if part.isdigit() else part.lower(),
304
            re.split(r"(\d+|\D+)", self.has_alias(stat[key]) or stat[key])
305
        )))
306
307
    @short_system_name.setter
308
    def short_system_name(self, short_name):
309
        """Set the short detected OS name (SNMP)."""
310
        self._short_system_name = short_name
311
312
    def set_stats(self, input_stats):
313
        """Set the stats to input_stats."""
314
        self.stats = input_stats
315
316
    def get_stats_snmp(self, bulk=False, snmp_oid=None):
317
        """Update stats using SNMP.
318
319
        If bulk=True, use a bulk request instead of a get request.
320
        """
321
        snmp_oid = snmp_oid or {}
322
323
        from glances.snmp import GlancesSNMPClient
324
325
        # Init the SNMP request
326
        clientsnmp = GlancesSNMPClient(host=self.args.client,
327
                                       port=self.args.snmp_port,
328
                                       version=self.args.snmp_version,
329
                                       community=self.args.snmp_community)
330
331
        # Process the SNMP request
332
        ret = {}
333
        if bulk:
334
            # Bulk request
335
            snmpresult = clientsnmp.getbulk_by_oid(0, 10, itervalues(*snmp_oid))
336
337
            if len(snmp_oid) == 1:
338
                # Bulk command for only one OID
339
                # Note: key is the item indexed but the OID result
340
                for item in snmpresult:
341
                    if iterkeys(item)[0].startswith(itervalues(snmp_oid)[0]):
342
                        ret[iterkeys(snmp_oid)[0] + iterkeys(item)
343
                            [0].split(itervalues(snmp_oid)[0])[1]] = itervalues(item)[0]
344
            else:
345
                # Build the internal dict with the SNMP result
346
                # Note: key is the first item in the snmp_oid
347
                index = 1
348
                for item in snmpresult:
349
                    item_stats = {}
350
                    item_key = None
351
                    for key in iterkeys(snmp_oid):
352
                        oid = snmp_oid[key] + '.' + str(index)
353
                        if oid in item:
354
                            if item_key is None:
355
                                item_key = item[oid]
356
                            else:
357
                                item_stats[key] = item[oid]
358
                    if item_stats:
359
                        ret[item_key] = item_stats
360
                    index += 1
361
        else:
362
            # Simple get request
363
            snmpresult = clientsnmp.get_by_oid(itervalues(*snmp_oid))
364
365
            # Build the internal dict with the SNMP result
366
            for key in iterkeys(snmp_oid):
367
                ret[key] = snmpresult[snmp_oid[key]]
368
369
        return ret
370
371
    def get_raw(self):
372
        """Return the stats object."""
373
        return self.stats
374
375
    def get_export(self):
376
        """Return the stats object to export."""
377
        return self.get_raw()
378
379
    def get_stats(self):
380
        """Return the stats object in JSON format."""
381
        return self._json_dumps(self.stats)
382
383
    def get_stats_item(self, item):
384
        """Return the stats object for a specific item in JSON format.
385
386
        Stats should be a list of dict (processlist, network...)
387
        """
388 View Code Duplication
        if isinstance(self.stats, dict):
389
            try:
390
                return self._json_dumps({item: self.stats[item]})
391
            except KeyError as e:
392
                logger.error("Cannot get item {} ({})".format(item, e))
393
                return None
394
        elif isinstance(self.stats, list):
395
            try:
396
                # Source:
397
                # http://stackoverflow.com/questions/4573875/python-get-index-of-dictionary-item-in-list
398
                # But https://github.com/nicolargo/glances/issues/1401
399
                return self._json_dumps({item: list(map(itemgetter(item), self.stats))})
400
            except (KeyError, ValueError) as e:
401
                logger.error("Cannot get item {} ({})".format(item, e))
402
                return None
403
        else:
404
            return None
405
406
    def get_stats_value(self, item, value):
407
        """Return the stats object for a specific item=value in JSON format.
408
409
        Stats should be a list of dict (processlist, network...)
410
        """
411
        if not isinstance(self.stats, list):
412
            return None
413
        else:
414
            if value.isdigit():
415
                value = int(value)
416
            try:
417
                return self._json_dumps({value: [i for i in self.stats if i[item] == value]})
418
            except (KeyError, ValueError) as e:
419
                logger.error(
420
                    "Cannot get item({})=value({}) ({})".format(item, value, e))
421
                return None
422
423
    def update_views(self):
424
        """Update the stats views.
425
426
        The V of MVC
427
        A dict of dict with the needed information to display the stats.
428
        Example for the stat xxx:
429
        'xxx': {'decoration': 'DEFAULT',
430
                'optional': False,
431
                'additional': False,
432
                'splittable': False}
433
        """
434
        ret = {}
435
436
        if (isinstance(self.get_raw(), list) and
437
                self.get_raw() is not None and
438
                self.get_key() is not None):
439
            # Stats are stored in a list of dict (ex: NETWORK, FS...)
440
            for i in self.get_raw():
441
                ret[i[self.get_key()]] = {}
442
                for key in listkeys(i):
443
                    value = {'decoration': 'DEFAULT',
444
                             'optional': False,
445
                             'additional': False,
446
                             'splittable': False}
447
                    ret[i[self.get_key()]][key] = value
448
        elif isinstance(self.get_raw(), dict) and self.get_raw() is not None:
449
            # Stats are stored in a dict (ex: CPU, LOAD...)
450
            for key in listkeys(self.get_raw()):
451
                value = {'decoration': 'DEFAULT',
452
                         'optional': False,
453
                         'additional': False,
454
                         'splittable': False}
455
                ret[key] = value
456
457
        self.views = ret
458
459
        return self.views
460
461
    def set_views(self, input_views):
462
        """Set the views to input_views."""
463
        self.views = input_views
464
465
    def get_views(self, item=None, key=None, option=None):
466
        """Return the views object.
467
468
        If key is None, return all the view for the current plugin
469
        else if option is None return the view for the specific key (all option)
470
        else return the view fo the specific key/option
471
472
        Specify item if the stats are stored in a dict of dict (ex: NETWORK, FS...)
473
        """
474
        if item is None:
475
            item_views = self.views
476
        else:
477
            item_views = self.views[item]
478
479
        if key is None:
480
            return item_views
481
        else:
482
            if option is None:
483
                return item_views[key]
484
            else:
485
                if option in item_views[key]:
486
                    return item_views[key][option]
487
                else:
488
                    return 'DEFAULT'
489
490
    def get_json_views(self, item=None, key=None, option=None):
491
        """Return the views (in JSON)."""
492
        return self._json_dumps(self.get_views(item, key, option))
493
494
    def load_limits(self, config):
495
        """Load limits from the configuration file, if it exists."""
496
        # By default set the history length to 3 points per second during one day
497
        self._limits['history_size'] = 28800
498
499
        if not hasattr(config, 'has_section'):
500
            return False
501
502
        # Read the global section
503
        if config.has_section('global'):
504
            self._limits['history_size'] = config.get_float_value('global', 'history_size', default=28800)
505
            logger.debug("Load configuration key: {} = {}".format('history_size', self._limits['history_size']))
506
507
        # Read the plugin specific section
508
        if config.has_section(self.plugin_name):
509
            for level, _ in config.items(self.plugin_name):
510
                # Read limits
511
                limit = '_'.join([self.plugin_name, level])
512
                try:
513
                    self._limits[limit] = config.get_float_value(self.plugin_name, level)
514
                except ValueError:
515
                    self._limits[limit] = config.get_value(self.plugin_name, level).split(",")
516
                logger.debug("Load limit: {} = {}".format(limit, self._limits[limit]))
517
518
        return True
519
520
    @property
521
    def limits(self):
522
        """Return the limits object."""
523
        return self._limits
524
525
    @limits.setter
526
    def limits(self, input_limits):
527
        """Set the limits to input_limits."""
528
        self._limits = input_limits
529
530
    def get_stats_action(self):
531
        """Return stats for the action.
532
533
        By default return all the stats.
534
        Can be overwrite by plugins implementation.
535
        For example, Docker will return self.stats['containers']
536
        """
537
        return self.stats
538
539
    def get_stat_name(self, header=""):
540
        """"Return the stat name with an optional header"""
541
        ret = self.plugin_name
542
        if header != "":
543
            ret += '_' + header
544
        return ret
545
546
    def get_alert(self,
547
                  current=0,
548
                  minimum=0,
549
                  maximum=100,
550
                  highlight_zero=True,
551
                  is_max=False,
552
                  header="",
553
                  action_key=None,
554
                  log=False):
555
        """Return the alert status relative to a current value.
556
557
        Use this function for minor stats.
558
559
        If current < CAREFUL of max then alert = OK
560
        If current > CAREFUL of max then alert = CAREFUL
561
        If current > WARNING of max then alert = WARNING
562
        If current > CRITICAL of max then alert = CRITICAL
563
564
        If highlight=True than 0.0 is highlighted
565
566
        If defined 'header' is added between the plugin name and the status.
567
        Only useful for stats with several alert status.
568
569
        If defined, 'action_key' define the key for the actions.
570
        By default, the action_key is equal to the header.
571
572
        If log=True than add log if necessary
573
        elif log=False than do not log
574
        elif log=None than apply the config given in the conf file
575
        """
576
        # Manage 0 (0.0) value if highlight_zero is not True
577
        if not highlight_zero and current == 0:
578
            return 'DEFAULT'
579
580
        # Compute the %
581
        try:
582
            value = (current * 100) / maximum
583
        except ZeroDivisionError:
584
            return 'DEFAULT'
585
        except TypeError:
586
            return 'DEFAULT'
587
588
        # Build the stat_name
589
        stat_name = self.get_stat_name(header=header)
590
591
        # Manage limits
592
        # If is_max is set then display the value in MAX
593
        ret = 'MAX' if is_max else 'OK'
594
        try:
595
            if value >= self.get_limit('critical', stat_name=stat_name):
596
                ret = 'CRITICAL'
597
            elif value >= self.get_limit('warning', stat_name=stat_name):
598
                ret = 'WARNING'
599
            elif value >= self.get_limit('careful', stat_name=stat_name):
600
                ret = 'CAREFUL'
601
            elif current < minimum:
602
                ret = 'CAREFUL'
603
        except KeyError:
604
            return 'DEFAULT'
605
606
        # Manage log
607
        log_str = ""
608
        if self.get_limit_log(stat_name=stat_name, default_action=log):
609
            # Add _LOG to the return string
610
            # So stats will be highlited with a specific color
611
            log_str = "_LOG"
612
            # Add the log to the list
613
            glances_events.add(ret, stat_name.upper(), value)
614
615
        # Manage threshold
616
        self.manage_threshold(stat_name, ret)
617
618
        # Manage action
619
        self.manage_action(stat_name, ret.lower(), header, action_key)
620
621
        # Default is 'OK'
622
        return ret + log_str
623
624
    def manage_threshold(self,
625
                         stat_name,
626
                         trigger):
627
        """Manage the threshold for the current stat."""
628
        glances_thresholds.add(stat_name, trigger)
629
630
    def manage_action(self,
631
                      stat_name,
632
                      trigger,
633
                      header,
634
                      action_key):
635
        """Manage the action for the current stat."""
636
        # Here is a command line for the current trigger ?
637
        try:
638
            command, repeat = self.get_limit_action(trigger, stat_name=stat_name)
639
        except KeyError:
640
            # Reset the trigger
641
            self.actions.set(stat_name, trigger)
642
        else:
643
            # Define the action key for the stats dict
644
            # If not define, then it sets to header
645
            if action_key is None:
646
                action_key = header
647
648
            # A command line is available for the current alert
649
            # 1) Build the {{mustache}} dictionnary
650
            if isinstance(self.get_stats_action(), list):
651
                # If the stats are stored in a list of dict (fs plugin for exemple)
652
                # Return the dict for the current header
653
                mustache_dict = {}
654
                for item in self.get_stats_action():
655
                    if item[self.get_key()] == action_key:
656
                        mustache_dict = item
657
                        break
658
            else:
659
                # Use the stats dict
660
                mustache_dict = self.get_stats_action()
661
            # 2) Run the action
662
            self.actions.run(
663
                stat_name, trigger,
664
                command, repeat, mustache_dict=mustache_dict)
665
666
    def get_alert_log(self,
667
                      current=0,
668
                      minimum=0,
669
                      maximum=100,
670
                      header="",
671
                      action_key=None):
672
        """Get the alert log."""
673
        return self.get_alert(current=current,
674
                              minimum=minimum,
675
                              maximum=maximum,
676
                              header=header,
677
                              action_key=action_key,
678
                              log=True)
679
680
    def get_limit(self, criticity, stat_name=""):
681
        """Return the limit value for the alert."""
682
        # Get the limit for stat + header
683
        # Exemple: network_wlan0_rx_careful
684
        try:
685
            limit = self._limits[stat_name + '_' + criticity]
686
        except KeyError:
687
            # Try fallback to plugin default limit
688
            # Exemple: network_careful
689
            limit = self._limits[self.plugin_name + '_' + criticity]
690
691
        # logger.debug("{} {} value is {}".format(stat_name, criticity, limit))
692
693
        # Return the limiter
694
        return limit
695
696
    def get_limit_action(self, criticity, stat_name=""):
697
        """Return the tuple (action, repeat) for the alert.
698
699
        - action is a command line
700
        - repeat is a bool
701
        """
702
        # Get the action for stat + header
703
        # Exemple: network_wlan0_rx_careful_action
704
        # Action key available ?
705
        ret = [(stat_name + '_' + criticity + '_action', False),
706
               (stat_name + '_' + criticity + '_action_repeat', True),
707
               (self.plugin_name + '_' + criticity + '_action', False),
708
               (self.plugin_name + '_' + criticity + '_action_repeat', True)]
709
        for r in ret:
710
            if r[0] in self._limits:
711
                return self._limits[r[0]], r[1]
712
713
        # No key found, the raise an error
714
        raise KeyError
715
716
    def get_limit_log(self, stat_name, default_action=False):
717
        """Return the log tag for the alert."""
718
        # Get the log tag for stat + header
719
        # Exemple: network_wlan0_rx_log
720
        try:
721
            log_tag = self._limits[stat_name + '_log']
722
        except KeyError:
723
            # Try fallback to plugin default log
724
            # Exemple: network_log
725
            try:
726
                log_tag = self._limits[self.plugin_name + '_log']
727
            except KeyError:
728
                # By defaukt, log are disabled
729
                return default_action
730
731
        # Return the action list
732
        return log_tag[0].lower() == 'true'
733
734
    def get_conf_value(self, value, header="", plugin_name=None, default=[]):
735
        """Return the configuration (header_) value for the current plugin.
736
737
        ...or the one given by the plugin_name var.
738
        """
739
        if plugin_name is None:
740
            # If not default use the current plugin name
741
            plugin_name = self.plugin_name
742
743
        if header != "":
744
            # Add the header
745
            plugin_name = plugin_name + '_' + header
746
747
        try:
748
            return self._limits[plugin_name + '_' + value]
749
        except KeyError:
750
            return default
751
752
    def is_hide(self, value, header=""):
753
        """Return True if the value is in the hide configuration list.
754
755
        The hide configuration list is defined in the glances.conf file.
756
        It is a comma separed list of regexp.
757
        Example for diskio:
758
        hide=sda2,sda5,loop.*
759
        """
760
        # TODO: possible optimisation: create a re.compile list
761
        return not all(j is None for j in [re.match(i, value.lower()) for i in self.get_conf_value('hide', header=header)])
762
763
    def has_alias(self, header):
764
        """Return the alias name for the relative header or None if nonexist."""
765
        try:
766
            # Force to lower case (issue #1126)
767
            return self._limits[self.plugin_name + '_' + header.lower() + '_' + 'alias'][0]
768
        except (KeyError, IndexError):
769
            # logger.debug("No alias found for {}".format(header))
770
            return None
771
772
    def msg_curse(self, args=None, max_width=None):
773
        """Return default string to display in the curse interface."""
774
        return [self.curse_add_line(str(self.stats))]
775
776
    def get_stats_display(self, args=None, max_width=None):
777
        """Return a dict with all the information needed to display the stat.
778
779
        key     | description
780
        ----------------------------
781
        display | Display the stat (True or False)
782
        msgdict | Message to display (list of dict [{ 'msg': msg, 'decoration': decoration } ... ])
783
        align   | Message position
784
        """
785
        display_curse = False
786
787
        if hasattr(self, 'display_curse'):
788
            display_curse = self.display_curse
789
        if hasattr(self, 'align'):
790
            align_curse = self._align
791
792
        if max_width is not None:
793
            ret = {'display': display_curse,
794
                   'msgdict': self.msg_curse(args, max_width=max_width),
795
                   'align': align_curse}
796
        else:
797
            ret = {'display': display_curse,
798
                   'msgdict': self.msg_curse(args),
799
                   'align': align_curse}
800
801
        return ret
802
803
    def curse_add_line(self, msg, decoration="DEFAULT",
804
                       optional=False, additional=False,
805
                       splittable=False):
806
        """Return a dict with.
807
808
        Where:
809
            msg: string
810
            decoration:
811
                DEFAULT: no decoration
812
                UNDERLINE: underline
813
                BOLD: bold
814
                TITLE: for stat title
815
                PROCESS: for process name
816
                STATUS: for process status
817
                NICE: for process niceness
818
                CPU_TIME: for process cpu time
819
                OK: Value is OK and non logged
820
                OK_LOG: Value is OK and logged
821
                CAREFUL: Value is CAREFUL and non logged
822
                CAREFUL_LOG: Value is CAREFUL and logged
823
                WARNING: Value is WARINING and non logged
824
                WARNING_LOG: Value is WARINING and logged
825
                CRITICAL: Value is CRITICAL and non logged
826
                CRITICAL_LOG: Value is CRITICAL and logged
827
            optional: True if the stat is optional (display only if space is available)
828
            additional: True if the stat is additional (display only if space is available after optional)
829
            spittable: Line can be splitted to fit on the screen (default is not)
830
        """
831
        return {'msg': msg, 'decoration': decoration, 'optional': optional, 'additional': additional, 'splittable': splittable}
832
833
    def curse_new_line(self):
834
        """Go to a new line."""
835
        return self.curse_add_line('\n')
836
837
    @property
838
    def align(self):
839
        """Get the curse align."""
840
        return self._align
841
842
    @align.setter
843
    def align(self, value):
844
        """Set the curse align.
845
846
        value: left, right, bottom.
847
        """
848
        self._align = value
849
850
    def auto_unit(self, number,
851
                  low_precision=False,
852
                  min_symbol='K'
853
                  ):
854
        """Make a nice human-readable string out of number.
855
856
        Number of decimal places increases as quantity approaches 1.
857
        CASE: 613421788        RESULT:       585M low_precision:       585M
858
        CASE: 5307033647       RESULT:      4.94G low_precision:       4.9G
859
        CASE: 44968414685      RESULT:      41.9G low_precision:      41.9G
860
        CASE: 838471403472     RESULT:       781G low_precision:       781G
861
        CASE: 9683209690677    RESULT:      8.81T low_precision:       8.8T
862
        CASE: 1073741824       RESULT:      1024M low_precision:      1024M
863
        CASE: 1181116006       RESULT:      1.10G low_precision:       1.1G
864
865
        :low_precision: returns less decimal places potentially (default is False)
866
                        sacrificing precision for more readability.
867
        :min_symbol: Do not approache if number < min_symbol (default is K)
868
        """
869
        symbols = ('K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y')
870
        if min_symbol in symbols:
871
            symbols = symbols[symbols.index(min_symbol):]
872
        prefix = {
873
            'Y': 1208925819614629174706176,
874
            'Z': 1180591620717411303424,
875
            'E': 1152921504606846976,
876
            'P': 1125899906842624,
877
            'T': 1099511627776,
878
            'G': 1073741824,
879
            'M': 1048576,
880
            'K': 1024
881
        }
882
883
        for symbol in reversed(symbols):
884
            value = float(number) / prefix[symbol]
885
            if value > 1:
886
                decimal_precision = 0
887
                if value < 10:
888
                    decimal_precision = 2
889
                elif value < 100:
890
                    decimal_precision = 1
891
                if low_precision:
892
                    if symbol in 'MK':
893
                        decimal_precision = 0
894
                    else:
895
                        decimal_precision = min(1, decimal_precision)
896
                elif symbol in 'K':
897
                    decimal_precision = 0
898
                return '{:.{decimal}f}{symbol}'.format(
899
                    value, decimal=decimal_precision, symbol=symbol)
900
        return '{!s}'.format(number)
901
902
    def trend_msg(self, trend, significant=1):
903
        """Return the trend message.
904
905
        Do not take into account if trend < significant
906
        """
907
        ret = '-'
908
        if trend is None:
909
            ret = ' '
910
        elif trend > significant:
911
            ret = '/'
912
        elif trend < -significant:
913
            ret = '\\'
914
        return ret
915
916
    def _check_decorator(fct):
917
        """Check if the plugin is enabled."""
918
        def wrapper(self, *args, **kw):
919
            if self.is_enable():
920
                ret = fct(self, *args, **kw)
921
            else:
922
                ret = self.stats
923
            return ret
924
        return wrapper
925
926
    def _log_result_decorator(fct):
927
        """Log (DEBUG) the result of the function fct."""
928
        def wrapper(*args, **kw):
929
            ret = fct(*args, **kw)
930
            logger.debug("%s %s %s return %s" % (
931
                args[0].__class__.__name__,
932
                args[0].__class__.__module__[len('glances_'):],
933
                fct.__name__, ret))
934
            return ret
935
        return wrapper
936
937
    # Mandatory to call the decorator in childs' classes
938
    _check_decorator = staticmethod(_check_decorator)
939
    _log_result_decorator = staticmethod(_log_result_decorator)
940