Completed
Push — develop ( 560490...abf64f )
by Nicolas
119:36 queued 116:37
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 config is not None:
91
            logger.debug('Load section {} in {}'.format(self.plugin_name,
92
                                                        config.config_file_paths()))
0 ignored issues
show
This line is too long as per the coding-style (84/80).

This check looks for lines that are too long. You can specify the maximum line length.

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