glances_plugin.py 23.7 KB
Newer Older
1 2
# -*- coding: utf-8 -*-
#
3
# This file is part of Glances.
4
#
5
# Copyright (C) 2015 Nicolargo <nicolas@nicolargo.com>
6 7 8 9 10 11 12 13 14 15 16 17 18
#
# Glances is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Glances is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
A
PEP 257  
Alessio Sergi 已提交
19

20 21
"""
I am your father...
A
PEP 257  
Alessio Sergi 已提交
22 23

...for all Glances plugins.
24
"""
25 26 27

# Import system libs
import json
28
from datetime import datetime
N
Nicolargo 已提交
29
from operator import itemgetter
30 31

# Import Glances lib
A
flake8  
Alessio Sergi 已提交
32
from glances.core.glances_actions import GlancesActions
A
Alessio Sergi 已提交
33 34
from glances.core.glances_globals import is_py3
from glances.core.glances_logging import logger
A
Alessio Sergi 已提交
35
from glances.core.glances_logs import glances_logs
36 37 38


class GlancesPlugin(object):
A
PEP 257  
Alessio Sergi 已提交
39

A
PEP 257  
Alessio Sergi 已提交
40
    """Main class for Glances plugin."""
41

42
    def __init__(self, args=None, items_history_list=None):
A
PEP 257  
Alessio Sergi 已提交
43
        """Init the plugin of plugins class."""
44 45
        # Plugin name (= module name without glances_)
        self.plugin_name = self.__class__.__module__[len('glances_'):]
A
Alessio Sergi 已提交
46
        # logger.debug("Init plugin %s" % self.plugin_name)
47

48 49 50
        # Init the args
        self.args = args

51
        # Init the default alignement (for curses)
52
        self._align = 'left'
53

54
        # Init the input method
55 56
        self._input_method = 'local'
        self._short_system_name = None
57

58 59 60
        # Init the stats list
        self.stats = None

61 62 63 64
        # Init the history list
        self.items_history_list = items_history_list
        self.stats_history = self.init_stats_history()

65
        # Init the limits dictionnary
66
        self._limits = dict()
67

68 69 70
        # Init the actions
        self.actions = GlancesActions()

N
Nicolargo 已提交
71
        # Init the views
72
        self.views = dict()
N
Nicolargo 已提交
73

74
    def __repr__(self):
A
PEP 257  
Alessio Sergi 已提交
75
        """Return the raw stats."""
76 77 78
        return self.stats

    def __str__(self):
A
PEP 257  
Alessio Sergi 已提交
79
        """Return the human-readable stats."""
80 81
        return str(self.stats)

82
    def get_key(self):
A
PEP 257  
Alessio Sergi 已提交
83
        """Return the key of the list."""
84 85
        return None

86
    def add_item_history(self, key, value):
A
PEP 257  
Alessio Sergi 已提交
87
        """Add an new item (key, value) to the current history."""
88 89 90 91 92
        try:
            self.stats_history[key].append(value)
        except KeyError:
            self.stats_history[key] = [value]

93
    def init_stats_history(self):
A
PEP 257  
Alessio Sergi 已提交
94
        """Init the stats history (dict of list)."""
95 96
        ret = None
        if self.args is not None and self.args.enable_history and self.get_items_history_list() is not None:
A
Alessio Sergi 已提交
97
            init_list = [i['name'] for i in self.get_items_history_list()]
98 99
            logger.debug("Stats history activated for plugin {0} (items: {0})".format(
                self.plugin_name, init_list))
100 101 102 103
            ret = {}
        return ret

    def reset_stats_history(self):
A
PEP 257  
Alessio Sergi 已提交
104
        """Reset the stats history (dict of list)."""
105
        if self.args is not None and self.args.enable_history and self.get_items_history_list() is not None:
A
Alessio Sergi 已提交
106
            reset_list = [i['name'] for i in self.get_items_history_list()]
107 108
            logger.debug("Reset history for plugin {0} (items: {0})".format(
                self.plugin_name, reset_list))
109 110
            self.stats_history = {}

111
    def update_stats_history(self, item_name=''):
A
PEP 257  
Alessio Sergi 已提交
112
        """Update stats history."""
113 114 115
        if (self.stats and self.args is not None and
                self.args.enable_history and
                self.get_items_history_list() is not None):
116
            self.add_item_history('date', datetime.now())
117
            for i in self.get_items_history_list():
118 119 120 121 122 123 124 125 126 127 128
                if type(self.stats) is list:
                    # Stats is a list of data
                    # Iter throught it (for exemple, iter throught network
                    # interface)
                    for l in self.stats:
                        self.add_item_history(
                            l[item_name] + '_' + i['name'], l[i['name']])
                else:
                    # Stats is not a list
                    # Add the item to the history directly
                    self.add_item_history(i['name'], self.stats[i['name']])
129 130

    def get_stats_history(self):
A
PEP 257  
Alessio Sergi 已提交
131
        """Return the stats history."""
132 133 134
        return self.stats_history

    def get_items_history_list(self):
A
PEP 257  
Alessio Sergi 已提交
135
        """Return the items history list."""
136 137
        return self.items_history_list

138 139 140 141 142 143 144
    @property
    def input_method(self):
        """Get the input method."""
        return self._input_method

    @input_method.setter
    def input_method(self, input_method):
A
PEP 257  
Alessio Sergi 已提交
145 146 147
        """Set the input method.

        * local: system local grab (psutil or direct access)
148 149 150
        * snmp: Client server mode via SNMP
        * glances: Client server mode via Glances API
        """
151
        self._input_method = input_method
152

153 154 155 156
    @property
    def short_system_name(self):
        """Get the short detected OS name (SNMP)."""
        return self._short_system_name
157

158 159 160 161
    @short_system_name.setter
    def short_system_name(self, short_name):
        """Set the short detected OS name (SNMP)."""
        self._short_system_name = short_name
N
Nicolargo 已提交
162

163
    def set_stats(self, input_stats):
A
PEP 257  
Alessio Sergi 已提交
164
        """Set the stats to input_stats."""
165 166
        self.stats = input_stats

167
    def get_stats_snmp(self, bulk=False, snmp_oid=None):
A
PEP 257  
Alessio Sergi 已提交
168 169 170 171
        """Update stats using SNMP.

        If bulk=True, use a bulk request instead of a get request.
        """
172 173
        snmp_oid = snmp_oid or {}

N
Nicolargo 已提交
174 175 176
        from glances.core.glances_snmp import GlancesSNMPClient

        # Init the SNMP request
177 178
        clientsnmp = GlancesSNMPClient(host=self.args.client,
                                       port=self.args.snmp_port,
N
Nicolargo 已提交
179
                                       version=self.args.snmp_version,
180
                                       community=self.args.snmp_community)
N
Nicolargo 已提交
181 182

        # Process the SNMP request
183
        ret = {}
184 185 186 187
        if bulk:
            # Bulk request
            snmpresult = clientsnmp.getbulk_by_oid(0, 10, *snmp_oid.values())

N
Nicolargo 已提交
188 189
            if len(snmp_oid) == 1:
                # Bulk command for only one OID
190
                # Note: key is the item indexed but the OID result
N
Nicolargo 已提交
191 192
                for item in snmpresult:
                    if item.keys()[0].startswith(snmp_oid.values()[0]):
193 194
                        ret[snmp_oid.keys()[0] + item.keys()
                            [0].split(snmp_oid.values()[0])[1]] = item.values()[0]
N
Nicolargo 已提交
195 196 197 198 199 200 201
            else:
                # Build the internal dict with the SNMP result
                # Note: key is the first item in the snmp_oid
                index = 1
                for item in snmpresult:
                    item_stats = {}
                    item_key = None
202
                    for key in list(snmp_oid.keys()):
N
Nicolargo 已提交
203 204 205 206 207 208
                        oid = snmp_oid[key] + '.' + str(index)
                        if oid in item:
                            if item_key is None:
                                item_key = item[oid]
                            else:
                                item_stats[key] = item[oid]
209
                    if item_stats:
N
Nicolargo 已提交
210 211
                        ret[item_key] = item_stats
                    index += 1
212 213 214 215 216
        else:
            # Simple get request
            snmpresult = clientsnmp.get_by_oid(*snmp_oid.values())

            # Build the internal dict with the SNMP result
217
            for key in list(snmp_oid.keys()):
218
                ret[key] = snmpresult[snmp_oid[key]]
219 220

        return ret
N
Nicolargo 已提交
221

222
    def get_raw(self):
A
PEP 257  
Alessio Sergi 已提交
223
        """Return the stats object."""
224 225 226
        return self.stats

    def get_stats(self):
A
PEP 257  
Alessio Sergi 已提交
227
        """Return the stats object in JSON format."""
228 229
        return json.dumps(self.stats)

N
Nicolargo 已提交
230
    def get_stats_item(self, item):
A
PEP 257  
Alessio Sergi 已提交
231 232
        """Return the stats object for a specific item in JSON format.

N
Nicolargo 已提交
233
        Stats should be a list of dict (processlist, network...)
234
        """
N
Nicolargo 已提交
235 236 237
        if type(self.stats) is not list:
            if type(self.stats) is dict:
                try:
238
                    return json.dumps({item: self.stats[item]})
N
Nicolargo 已提交
239
                except KeyError as e:
A
Alessio Sergi 已提交
240
                    logger.error("Cannot get item {0} ({1})".format(item, e))
N
Nicolargo 已提交
241 242 243 244
            else:
                return None
        else:
            try:
245 246 247
                # Source:
                # http://stackoverflow.com/questions/4573875/python-get-index-of-dictionary-item-in-list
                return json.dumps({item: map(itemgetter(item), self.stats)})
N
Nicolargo 已提交
248
            except (KeyError, ValueError) as e:
A
Alessio Sergi 已提交
249
                logger.error("Cannot get item {0} ({1})".format(item, e))
N
Nicolargo 已提交
250 251 252
                return None

    def get_stats_value(self, item, value):
A
PEP 257  
Alessio Sergi 已提交
253 254
        """Return the stats object for a specific item=value in JSON format.

N
Nicolargo 已提交
255 256 257 258 259 260 261 262
        Stats should be a list of dict (processlist, network...)
        """
        if type(self.stats) is not list:
            return None
        else:
            if value.isdigit():
                value = int(value)
            try:
263
                return json.dumps({value: [i for i in self.stats if i[item] == value]})
N
Nicolargo 已提交
264
            except (KeyError, ValueError) as e:
265 266
                logger.error(
                    "Cannot get item({0})=value({1}) ({2})".format(item, value, e))
N
Nicolargo 已提交
267 268
                return None

269
    def update_views(self):
A
PEP 257  
Alessio Sergi 已提交
270 271
        """Default builder fo the stats views.

272 273 274 275 276 277 278 279 280
        The V of MVC
        A dict of dict with the needed information to display the stats.
        Example for the stat xxx:
        'xxx': {'decoration': 'DEFAULT',
                'optional': False,
                'additional': False,
                'splittable': False}
        """
        ret = {}
281

282
        if type(self.get_raw()) is list and self.get_raw() is not None and self.get_key() is not None:
283 284 285 286 287 288 289 290 291
            # Stats are stored in a list of dict (ex: NETWORK, FS...)
            for i in self.get_raw():
                ret[i[self.get_key()]] = {}
                for key in i.keys():
                    value = {'decoration': 'DEFAULT',
                             'optional': False,
                             'additional': False,
                             'splittable': False}
                    ret[i[self.get_key()]][key] = value
292
        elif type(self.get_raw()) is dict and self.get_raw() is not None:
293 294 295 296 297 298 299 300
            # Stats are stored in a dict (ex: CPU, LOAD...)
            for key in self.get_raw().keys():
                value = {'decoration': 'DEFAULT',
                         'optional': False,
                         'additional': False,
                         'splittable': False}
                ret[key] = value

301
        self.views = ret
302

303 304 305
        return self.views

    def set_views(self, input_views):
N
Nicolargo 已提交
306 307 308
        """Set the views to input_views."""
        self.views = input_views

309
    def get_views(self, item=None, key=None, option=None):
N
Nicolargo 已提交
310
        """Return the views object.
A
PEP 257  
Alessio Sergi 已提交
311

312 313
        If key is None, return all the view for the current plugin
        else if option is None return the view for the specific key (all option)
314 315
        else return the view fo the specific key/option

A
PEP 257  
Alessio Sergi 已提交
316 317
        Specify item if the stats are stored in a dict of dict (ex: NETWORK, FS...)
        """
318 319 320 321 322
        if item is None:
            item_views = self.views
        else:
            item_views = self.views[item]

323
        if key is None:
324
            return item_views
325 326
        else:
            if option is None:
327
                return item_views[key]
328
            else:
329
                return item_views[key][option]
N
Nicolargo 已提交
330

331
    def load_limits(self, config):
332
        """Load limits from the configuration file, if it exists."""
333
        if (hasattr(config, 'has_section') and
334
                config.has_section(self.plugin_name)):
335
            for level, v in config.items(self.plugin_name):
336
                # Read limits
337
                limit = '_'.join([self.plugin_name, level])
338
                try:
339
                    self._limits[limit] = config.get_float_value(self.plugin_name, level)
A
Alessio Sergi 已提交
340
                except ValueError:
341
                    self._limits[limit] = config.get_value(self.plugin_name, level).split(",")
342
                logger.debug("Load limit: {0} = {1}".format(limit, self._limits[limit]))
343

344 345
    @property
    def limits(self):
A
PEP 257  
Alessio Sergi 已提交
346
        """Return the limits object."""
347 348 349 350 351 352
        return self._limits

    @limits.setter
    def limits(self, input_limits):
        """Set the limits to input_limits."""
        self._limits = input_limits
353

354
    def get_alert(self, current=0, minimum=0, maximum=100, header="", log=False):
A
PEP 257  
Alessio Sergi 已提交
355 356 357
        """Return the alert status relative to a current value.

        Use this function for minor stats.
358

A
PEP 257  
Alessio Sergi 已提交
359 360 361 362 363 364 365 366
        If current < CAREFUL of max then alert = OK
        If current > CAREFUL of max then alert = CAREFUL
        If current > WARNING of max then alert = WARNING
        If current > CRITICAL of max then alert = CRITICAL

        If defined 'header' is added between the plugin name and the status.
        Only useful for stats with several alert status.

367 368 369
        If log=True than add log if necessary
        elif log=False than do not log
        elig log=None than apply the config given in the conf file
A
PEP 257  
Alessio Sergi 已提交
370
        """
371 372
        # Compute the %
        try:
373
            value = (current * 100) / maximum
374 375 376 377 378
        except ZeroDivisionError:
            return 'DEFAULT'
        except TypeError:
            return 'DEFAULT'

N
Nicolargo 已提交
379 380 381 382 383 384
        # Build the stat_name = plugin_name + header
        if header == "":
            stat_name = self.plugin_name
        else:
            stat_name = self.plugin_name + '_' + header

385 386
        # Manage limits
        ret = 'OK'
387
        try:
N
Nicolargo 已提交
388
            if value > self.__get_limit('critical', stat_name=stat_name):
389
                ret = 'CRITICAL'
N
Nicolargo 已提交
390
            elif value > self.__get_limit('warning', stat_name=stat_name):
391
                ret = 'WARNING'
N
Nicolargo 已提交
392
            elif value > self.__get_limit('careful', stat_name=stat_name):
393
                ret = 'CAREFUL'
394
            elif current < minimum:
395 396 397
                ret = 'CAREFUL'
        except KeyError:
            return 'DEFAULT'
398

399
        # Manage log
N
Nicolargo 已提交
400
        log_str = ""
401
        if self.__get_limit_log(stat_name=stat_name, default_action=log):
402 403 404
            # Add _LOG to the return string
            # So stats will be highlited with a specific color
            log_str = "_LOG"
405
            # Add the log to the list
406 407
            glances_logs.add(ret, stat_name.upper(), value, [])

408
        # Manage action
N
Nicolargo 已提交
409
        # Here is a command line for the current trigger ?
410
        try:
N
Nicolargo 已提交
411
            command = self.__get_limit_action(ret.lower(), stat_name=stat_name)
412 413
        except KeyError:
            # Reset the trigger
N
Nicolargo 已提交
414
            self.actions.set(stat_name, ret.lower())
415 416 417 418 419 420
        else:
            # A command line is available for the current alert, run it
            # Build the {{mustache}} dictionnary
            if type(self.stats) is list:
                # If the stats are stored in a list of dict (fs plugin for exemple)
                # Return the dict for the current header
421 422 423 424 425
                mustache_dict = {}
                for item in self.stats:
                    if item[self.get_key()] == header:
                        mustache_dict = item
                        break
426 427 428
            else:
                # Use the stats dict
                mustache_dict = self.stats
429
            # Run the action
430 431
            self.actions.run(
                stat_name, ret.lower(), command, mustache_dict=mustache_dict)
432

433 434 435
        # Default is ok
        return ret + log_str

436
    def get_alert_log(self, current=0, minimum=0, maximum=100, header=""):
A
PEP 257  
Alessio Sergi 已提交
437
        """Get the alert log."""
438
        return self.get_alert(current, minimum, maximum, header, log=True)
439

N
Nicolargo 已提交
440
    def __get_limit(self, criticity, stat_name=""):
A
PEP 257  
Alessio Sergi 已提交
441
        """Return the limit value for the alert."""
442 443 444
        # Get the limit for stat + header
        # Exemple: network_wlan0_rx_careful
        try:
445
            limit = self._limits[stat_name + '_' + criticity]
446 447 448
        except KeyError:
            # Try fallback to plugin default limit
            # Exemple: network_careful
449
            limit = self._limits[self.plugin_name + '_' + criticity]
450 451 452

        # Return the limit
        return limit
453

N
Nicolargo 已提交
454
    def __get_limit_action(self, criticity, stat_name=""):
A
PEP 257  
Alessio Sergi 已提交
455
        """Return the action for the alert."""
456
        # Get the action for stat + header
457
        # Exemple: network_wlan0_rx_careful_action
458
        try:
459
            ret = self._limits[stat_name + '_' + criticity + '_action']
460
        except KeyError:
461 462
            # Try fallback to plugin default limit
            # Exemple: network_careful_action
463
            ret = self._limits[self.plugin_name + '_' + criticity + '_action']
464 465 466 467 468

        # Return the action list
        return ret

    def __get_limit_log(self, stat_name, default_action=False):
A
PEP 257  
Alessio Sergi 已提交
469
        """Return the log tag for the alert."""
470 471 472
        # Get the log tag for stat + header
        # Exemple: network_wlan0_rx_log
        try:
473
            log_tag = self._limits[stat_name + '_log']
474 475 476 477
        except KeyError:
            # Try fallback to plugin default log
            # Exemple: network_log
            try:
478
                log_tag = self._limits[self.plugin_name + '_log']
479 480 481
            except KeyError:
                # By defaukt, log are disabled
                return default_action
482

483
        # Return the action list
484
        return log_tag[0].lower() == 'true'
485

486
    def get_conf_value(self, value, header="", plugin_name=None):
A
PEP 257  
Alessio Sergi 已提交
487 488 489 490
        """Return the configuration (header_) value for the current plugin.

        ...or the one given by the plugin_name var.
        """
491
        if plugin_name is None:
N
nicolargo 已提交
492
            # If not default use the current plugin name
493
            plugin_name = self.plugin_name
N
nicolargo 已提交
494 495 496 497 498 499 500 501 502

        if header != "":
            # Add the header
            plugin_name = plugin_name + '_' + header

        try:
            return self._limits[plugin_name + '_' + value]
        except KeyError:
            return []
503 504

    def is_hide(self, value, header=""):
A
PEP 257  
Alessio Sergi 已提交
505
        """Return True if the value is in the hide configuration list."""
506
        return value in self.get_conf_value('hide', header=header)
507 508

    def has_alias(self, header):
A
PEP 257  
Alessio Sergi 已提交
509
        """Return the alias name for the relative header or None if nonexist."""
510
        try:
511
            return self._limits[self.plugin_name + '_' + header + '_' + 'alias'][0]
512 513
        except (KeyError, IndexError):
            return None
514

N
Nicolargo 已提交
515
    def msg_curse(self, args=None, max_width=None):
A
PEP 257  
Alessio Sergi 已提交
516
        """Return default string to display in the curse interface."""
517 518
        return [self.curse_add_line(str(self.stats))]

N
Nicolargo 已提交
519
    def get_stats_display(self, args=None, max_width=None):
A
PEP 257  
Alessio Sergi 已提交
520 521 522 523 524 525
        """Return a dict with all the information needed to display the stat.

        key     | description
        ----------------------------
        display | Display the stat (True or False)
        msgdict | Message to display (list of dict [{ 'msg': msg, 'decoration': decoration } ... ])
526
        align   | Message position
A
PEP 257  
Alessio Sergi 已提交
527
        """
528 529
        display_curse = False

530
        if hasattr(self, 'display_curse'):
531
            display_curse = self.display_curse
532
        if hasattr(self, 'align'):
533
            align_curse = self._align
534

N
Nicolargo 已提交
535 536
        if max_width is not None:
            ret = {'display': display_curse,
537 538
                   'msgdict': self.msg_curse(args, max_width=max_width),
                   'align': align_curse}
N
Nicolargo 已提交
539 540
        else:
            ret = {'display': display_curse,
541 542
                   'msgdict': self.msg_curse(args),
                   'align': align_curse}
N
Nicolargo 已提交
543 544

        return ret
545

546 547
    def curse_add_line(self, msg, decoration="DEFAULT",
                       optional=False, additional=False,
548
                       splittable=False):
A
PEP 257  
Alessio Sergi 已提交
549
        """Return a dict with.
A
PEP 257  
Alessio Sergi 已提交
550 551

        Where:
552 553 554 555 556 557
            msg: string
            decoration:
                DEFAULT: no decoration
                UNDERLINE: underline
                BOLD: bold
                TITLE: for stat title
558 559
                PROCESS: for process name
                STATUS: for process status
560
                NICE: for process niceness
561
                CPU_TIME: for process cpu time
562 563 564 565 566 567 568 569 570
                OK: Value is OK and non logged
                OK_LOG: Value is OK and logged
                CAREFUL: Value is CAREFUL and non logged
                CAREFUL_LOG: Value is CAREFUL and logged
                WARNING: Value is WARINING and non logged
                WARNING_LOG: Value is WARINING and logged
                CRITICAL: Value is CRITICAL and non logged
                CRITICAL_LOG: Value is CRITICAL and logged
            optional: True if the stat is optional (display only if space is available)
571
            additional: True if the stat is additional (display only if space is available after optional)
572
            spittable: Line can be splitted to fit on the screen (default is not)
573
        """
574
        return {'msg': msg, 'decoration': decoration, 'optional': optional, 'additional': additional, 'splittable': splittable}
575 576

    def curse_new_line(self):
A
PEP 257  
Alessio Sergi 已提交
577
        """Go to a new line."""
578 579
        return self.curse_add_line('\n')

580 581 582 583
    @property
    def align(self):
        """Get the curse align."""
        return self._align
N
Nicolargo 已提交
584

585 586 587 588 589 590 591
    @align.setter
    def align(self, value):
        """Set the curse align.

        value: left, right, bottom.
        """
        self._align = value
N
Nicolargo 已提交
592

A
Alessio Sergi 已提交
593
    def auto_unit(self, number, low_precision=False):
A
PEP 257  
Alessio Sergi 已提交
594 595 596
        """Make a nice human-readable string out of number.

        Number of decimal places increases as quantity approaches 1.
597 598 599 600 601 602 603 604 605 606

        examples:
        CASE: 613421788        RESULT:       585M low_precision:       585M
        CASE: 5307033647       RESULT:      4.94G low_precision:       4.9G
        CASE: 44968414685      RESULT:      41.9G low_precision:      41.9G
        CASE: 838471403472     RESULT:       781G low_precision:       781G
        CASE: 9683209690677    RESULT:      8.81T low_precision:       8.8T
        CASE: 1073741824       RESULT:      1024M low_precision:      1024M
        CASE: 1181116006       RESULT:      1.10G low_precision:       1.1G

A
PEP 257  
Alessio Sergi 已提交
607 608
        'low_precision=True' returns less decimal places potentially
        sacrificing precision for more readability.
609 610 611 612 613 614 615 616 617 618 619 620 621
        """
        symbols = ('K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y')
        prefix = {
            'Y': 1208925819614629174706176,
            'Z': 1180591620717411303424,
            'E': 1152921504606846976,
            'P': 1125899906842624,
            'T': 1099511627776,
            'G': 1073741824,
            'M': 1048576,
            'K': 1024
        }

A
Alessio Sergi 已提交
622 623
        for symbol in reversed(symbols):
            value = float(number) / prefix[symbol]
624
            if value > 1:
A
Alessio Sergi 已提交
625
                decimal_precision = 0
626
                if value < 10:
A
Alessio Sergi 已提交
627
                    decimal_precision = 2
628
                elif value < 100:
A
Alessio Sergi 已提交
629
                    decimal_precision = 1
630
                if low_precision:
A
Alessio Sergi 已提交
631 632
                    if symbol in 'MK':
                        decimal_precision = 0
633
                    else:
A
Alessio Sergi 已提交
634 635 636 637 638 639
                        decimal_precision = min(1, decimal_precision)
                elif symbol in 'K':
                    decimal_precision = 0
                return '{0:.{decimal}f}{symbol}'.format(
                    value, decimal=decimal_precision, symbol=symbol)
        return '{0!s}'.format(number)
640 641

    def _log_result_decorator(fct):
A
PEP 257  
Alessio Sergi 已提交
642
        """Log (DEBUG) the result of the function fct."""
643 644
        def wrapper(*args, **kw):
            ret = fct(*args, **kw)
N
Nicolargo 已提交
645 646 647 648 649 650
            if is_py3:
                logger.debug("%s %s %s return %s" % (args[0].__class__.__name__, args[
                             0].__class__.__module__[len('glances_'):], fct.__name__, ret))
            else:
                logger.debug("%s %s %s return %s" % (args[0].__class__.__name__, args[
                             0].__class__.__module__[len('glances_'):], fct.func_name, ret))
651 652 653 654 655
            return ret
        return wrapper

    # Mandatory to call the decorator in childs' classes
    _log_result_decorator = staticmethod(_log_result_decorator)