main.py 22.7 KB
Newer Older
N
Nicolas Hennion 已提交
1 2
# -*- coding: utf-8 -*-
#
3
# This file is part of Glances.
N
Nicolas Hennion 已提交
4
#
N
nicolargo 已提交
5
# Copyright (C) 2019 Nicolargo <nicolas@nicolargo.com>
N
Nicolas Hennion 已提交
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

"""Glances main class."""
N
Nicolas Hennion 已提交
21

N
Nicolas Hennion 已提交
22
import argparse
23
import sys
24
import tempfile
N
Nicolas Hennion 已提交
25

A
Alessio Sergi 已提交
26
from glances import __version__, psutil_version
N
nicolargo 已提交
27
from glances.compat import input
28
from glances.config import Config
29
from glances.globals import WINDOWS
30
from glances.logger import logger, LOG_FILENAME
N
Nicolas Hennion 已提交
31

N
Nicolas Hennion 已提交
32

33 34 35 36 37 38 39 40 41 42
def disable(class_name, var):
    """Set disable_<var> to True in the class class_name."""
    setattr(class_name, 'disable_' + var, True)


def enable(class_name, var):
    """Set disable_<var> to False in the class class_name."""
    setattr(class_name, 'disable_' + var, False)


43
class GlancesMain(object):
A
PEP 257  
Alessio Sergi 已提交
44
    """Main class to manage Glances instance."""
N
Nicolas Hennion 已提交
45

N
Nicolas Hennion 已提交
46 47
    # Default stats' refresh time is 3 seconds
    refresh_time = 3
N
Nicolas Hennion 已提交
48
    # Set the default cache lifetime to 1 second (only for server)
A
Alessio Sergi 已提交
49
    cached_time = 1
N
Nicolas Hennion 已提交
50 51 52 53
    # By default, Glances is ran in standalone mode (no client/server)
    client_tag = False
    # Server TCP port number (default is 61209)
    server_port = 61209
N
Nicolas Hennion 已提交
54 55
    # Web Server TCP port number (default is 61208)
    web_server_port = 61208
N
Nicolas Hennion 已提交
56 57 58 59
    # Default username/password for client/server mode
    username = "glances"
    password = ""

60 61 62 63 64 65
    # Examples of use
    example_of_use = """
Examples of use:
  Monitor local machine (standalone mode):
    $ glances

66 67 68
  Display all Glances modules (plugins and exporters) and exit:
    $ glances --module-list

A
RESTful  
Alessio Sergi 已提交
69
  Monitor local machine with the Web interface and start RESTful server:
70 71 72
    $ glances -w
    Glances web server started on http://0.0.0.0:61208/

A
RESTful  
Alessio Sergi 已提交
73
  Only start RESTful API (without the WebUI):
74 75 76
    $ glances -w --disable-webui
    Glances API available on http://0.0.0.0:61208/api/

77
  Monitor local machine and export stats to a CSV file (standalone mode):
78
    $ glances --export csv --export-csv-file /tmp/glances.csv
79 80

  Monitor local machine and export stats to a InfluxDB server with 5s refresh time (standalone mode):
81
    $ glances -t 5 --export influxdb
82

83
  Start a Glances XML/RCP server (server mode):
84 85
    $ glances -s

86
  Connect Glances to a Glances XML/RCP server (client mode):
87 88 89
    $ glances -c <ip_server>

  Connect Glances to a Glances server and export stats to a StatsD server (client mode):
90
    $ glances -c <ip_server> --export statsd
91 92 93

  Start the client browser (browser mode):
    $ glances --browser
94

95 96 97 98 99
  Display stats to stdout (one stat per line):
    $ glances --stdout now,cpu.user,mem.used,load

  Display CSV stats to stdout (all stats in one line):
    $ glances --stdout-csv now,cpu.user,mem.used,load
100

101
  Disable some plugins (comma separated list):
102
    $ glances --disable-plugin network,ports
103 104 105

  Enable some plugins (comma separated list):
    $ glances --enable-plugin sensors
106
"""
N
Nicolargo 已提交
107

N
Nicolas Hennion 已提交
108
    def __init__(self):
A
Alessio Sergi 已提交
109
        """Manage the command line arguments."""
110
        # Read the command line arguments
A
Alessio Sergi 已提交
111 112 113 114
        self.args = self.parse_args()

    def init_args(self):
        """Init all the command line arguments."""
115
        version = 'Glances v{} with PsUtil v{}\nLog file: {}'.format(__version__, psutil_version, LOG_FILENAME)
116
        parser = argparse.ArgumentParser(
A
Alessio Sergi 已提交
117
            prog='glances',
N
Nicolargo 已提交
118 119 120
            conflict_handler='resolve',
            formatter_class=argparse.RawDescriptionHelpFormatter,
            epilog=self.example_of_use)
121
        parser.add_argument(
122
            '-V', '--version', action='version', version=version)
N
Nicolas Hennion 已提交
123
        parser.add_argument('-d', '--debug', action='store_true', default=False,
A
Alessio Sergi 已提交
124
                            dest='debug', help='enable debug mode')
A
Alessio Sergi 已提交
125
        parser.add_argument('-C', '--config', dest='conf_file',
A
Alessio Sergi 已提交
126
                            help='path to the configuration file')
127
        # Disable plugin
N
nicolargo 已提交
128 129 130 131
        parser.add_argument('--modules-list', '--module-list',
                            action='store_true', default=False,
                            dest='modules_list',
                            help='display modules (plugins & exports) list and exit')
132
        parser.add_argument('--disable-plugin', '--disable-plugins', dest='disable_plugin',
133
                            help='disable plugin (comma separed list)')
134 135
        parser.add_argument('--enable-plugin', '--enable-plugins', dest='enable_plugin',
                            help='enable plugin (comma separed list)')
N
nicolargo 已提交
136 137
        parser.add_argument('--disable-process', action='store_true', default=False,
                            dest='disable_process', help='disable process module')
138
        # Enable or disable option
139 140
        parser.add_argument('--disable-webui', action='store_true', default=False,
                            dest='disable_webui', help='disable the Web Interface')
141 142 143
        parser.add_argument('--light', '--enable-light', action='store_true',
                            default=False, dest='enable_light',
                            help='light mode for Curses UI (disable all but top menu)')
N
nicolargo 已提交
144 145 146 147
        parser.add_argument('-0', '--disable-irix', action='store_true', default=False,
                            dest='disable_irix', help='task\'s cpu usage will be divided by the total number of CPUs')
        parser.add_argument('-1', '--percpu', action='store_true', default=False,
                            dest='percpu', help='start Glances in per CPU mode')
148
        parser.add_argument('-2', '--disable-left-sidebar', action='store_true',
A
Alessio Sergi 已提交
149
                            default=False, dest='disable_left_sidebar',
150
                            help='disable network, disk I/O, FS and sensors modules')
N
nicolargo 已提交
151 152 153 154 155 156 157
        parser.add_argument('-3', '--disable-quicklook', action='store_true', default=False,
                            dest='disable_quicklook', help='disable quick look module')
        parser.add_argument('-4', '--full-quicklook', action='store_true', default=False,
                            dest='full_quicklook', help='disable all but quick look and load')
        parser.add_argument('-5', '--disable-top', action='store_true',
                            default=False, dest='disable_top',
                            help='disable top menu (QL, CPU, MEM, SWAP and LOAD)')
158 159
        parser.add_argument('-6', '--meangpu', action='store_true', default=False,
                            dest='meangpu', help='start Glances in mean GPU mode')
N
Nicolargo 已提交
160 161
        parser.add_argument('--disable-history', action='store_true', default=False,
                            dest='disable_history', help='disable stats history')
162
        parser.add_argument('--disable-bold', action='store_true', default=False,
A
Alessio Sergi 已提交
163
                            dest='disable_bold', help='disable bold mode in the terminal')
164
        parser.add_argument('--disable-bg', action='store_true', default=False,
165
                            dest='disable_bg', help='disable background colors in the terminal')
166 167
        parser.add_argument('--enable-irq', action='store_true', default=False,
                            dest='enable_irq', help='enable IRQ module'),
N
Nicolargo 已提交
168
        parser.add_argument('--enable-process-extended', action='store_true', default=False,
A
Alessio Sergi 已提交
169
                            dest='enable_process_extended', help='enable extended stats on top process')
170
        # Export modules feature
171 172 173 174 175 176
        parser.add_argument('--export', dest='export',
                            help='enable export module (comma separed list)')
        parser.add_argument('--export-csv-file',
                            default='./glances.csv',
                            dest='export_csv_file',
                            help='file path for CSV exporter')
177 178
        parser.add_argument('--export-csv-overwrite', action='store_true', default=False,
                            dest='export_csv_overwrite', help='overwrite existing CSV file')
179 180 181 182
        parser.add_argument('--export-json-file',
                            default='./glances.json',
                            dest='export_json_file',
                            help='file path for JSON exporter')
N
nicolargo 已提交
183
        parser.add_argument('--export-graph-path',
184
                            default=tempfile.gettempdir(),
N
nicolargo 已提交
185 186
                            dest='export_graph_path',
                            help='Folder for Graph exporter')
N
Nicolargo 已提交
187 188
        # Client/Server option
        parser.add_argument('-c', '--client', dest='client',
A
Alessio Sergi 已提交
189
                            help='connect to a Glances server by IPv4/IPv6 address or hostname')
N
Nicolargo 已提交
190
        parser.add_argument('-s', '--server', action='store_true', default=False,
A
Alessio Sergi 已提交
191
                            dest='server', help='run Glances in server mode')
192
        parser.add_argument('--browser', action='store_true', default=False,
A
Alessio Sergi 已提交
193
                            dest='browser', help='start the client browser (list of servers)')
194
        parser.add_argument('--disable-autodiscover', action='store_true', default=False,
A
Alessio Sergi 已提交
195
                            dest='disable_autodiscover', help='disable autodiscover feature')
196
        parser.add_argument('-p', '--port', default=None, type=int, dest='port',
197
                            help='define the client/server TCP port [default: {}]'.format(self.server_port))
N
Nicolargo 已提交
198
        parser.add_argument('-B', '--bind', default='0.0.0.0', dest='bind_address',
A
Alessio Sergi 已提交
199
                            help='bind server to the given IPv4/IPv6 address or hostname')
200 201
        parser.add_argument('--username', action='store_true', default=False, dest='username_prompt',
                            help='define a client/server username')
A
Alessio Sergi 已提交
202
        parser.add_argument('--password', action='store_true', default=False, dest='password_prompt',
A
Alessio Sergi 已提交
203
                            help='define a client/server password')
204 205
        parser.add_argument('-u', dest='username_used',
                            help='use the given client/server username')
206
        parser.add_argument('--snmp-community', default='public', dest='snmp_community',
A
Alessio Sergi 已提交
207
                            help='SNMP community')
A
Alessio Sergi 已提交
208
        parser.add_argument('--snmp-port', default=161, type=int,
A
Alessio Sergi 已提交
209
                            dest='snmp_port', help='SNMP port')
N
Nicolargo 已提交
210
        parser.add_argument('--snmp-version', default='2c', dest='snmp_version',
A
Alessio Sergi 已提交
211
                            help='SNMP version (1, 2c or 3)')
N
Nicolargo 已提交
212
        parser.add_argument('--snmp-user', default='private', dest='snmp_user',
A
Alessio Sergi 已提交
213
                            help='SNMP username (only for SNMPv3)')
N
Nicolargo 已提交
214
        parser.add_argument('--snmp-auth', default='password', dest='snmp_auth',
A
Alessio Sergi 已提交
215
                            help='SNMP authentication key (only for SNMPv3)')
216
        parser.add_argument('--snmp-force', action='store_true', default=False,
A
Alessio Sergi 已提交
217
                            dest='snmp_force', help='force SNMP mode')
218
        parser.add_argument('-t', '--time', default=self.refresh_time, type=float,
219
                            dest='time', help='set refresh time in seconds [default: {} sec]'.format(self.refresh_time))
A
Alessio Sergi 已提交
220
        parser.add_argument('-w', '--webserver', action='store_true', default=False,
A
Alessio Sergi 已提交
221
                            dest='webserver', help='run Glances in web server mode (bottle needed)')
222 223
        parser.add_argument('--cached-time', default=self.cached_time, type=int,
                            dest='cached_time', help='set the server cache time [default: {} sec]'.format(self.cached_time))
N
nicolargo 已提交
224 225
        parser.add_argument('--open-web-browser', action='store_true', default=False,
                            dest='open_web_browser', help='try to open the Web UI in the default Web browser')
226
        # Display options
N
nicolargo 已提交
227 228
        parser.add_argument('-q', '--quiet', default=False, action='store_true',
                            dest='quiet', help='do not display the curses interface')
N
Nicolargo 已提交
229
        parser.add_argument('-f', '--process-filter', default=None, type=str,
A
Alessio Sergi 已提交
230
                            dest='process_filter', help='set the process filter pattern (regular expression)')
231
        parser.add_argument('--process-short-name', action='store_true', default=False,
A
Alessio Sergi 已提交
232
                            dest='process_short_name', help='force short name for processes name')
233
        parser.add_argument('--stdout', default=None,
234 235 236
                            dest='stdout', help='display stats to stdout, one stat per line (comma separated list of plugins/plugins.attribute)')
        parser.add_argument('--stdout-csv', default=None,
                            dest='stdout_csv', help='display stats to stdout, csv format (comma separated list of plugins/plugins.attribute)')
A
Alessio Sergi 已提交
237
        if not WINDOWS:
238
            parser.add_argument('--hide-kernel-threads', action='store_true', default=False,
239
                                dest='no_kernel_threads', help='hide kernel threads in process list (not available on Windows)')
N
Nicolargo 已提交
240
        parser.add_argument('-b', '--byte', action='store_true', default=False,
A
Alessio Sergi 已提交
241
                            dest='byte', help='display network rate in byte per second')
242 243
        parser.add_argument('--diskio-show-ramfs', action='store_true', default=False,
                            dest='diskio_show_ramfs', help='show RAM Fs in the DiskIO plugin')
244 245
        parser.add_argument('--diskio-iops', action='store_true', default=False,
                            dest='diskio_iops', help='show IO per second in the DiskIO plugin')
N
nicolargo 已提交
246 247
        parser.add_argument('--fahrenheit', action='store_true', default=False,
                            dest='fahrenheit', help='display temperature in Fahrenheit (default is Celsius)')
A
Alessio Sergi 已提交
248
        parser.add_argument('--fs-free-space', action='store_true', default=False,
A
Alessio Sergi 已提交
249
                            dest='fs_free_space', help='display FS free space instead of used')
250
        parser.add_argument('--sparkline', action='store_true', default=False,
N
nicolargo 已提交
251
                            dest='sparkline', help='display sparklines instead of bar in the curses interface')
N
Nicolargo 已提交
252
        parser.add_argument('--theme-white', action='store_true', default=False,
A
Alessio Sergi 已提交
253
                            dest='theme_white', help='optimize display colors for white background')
254 255 256
        # Globals options
        parser.add_argument('--disable-check-update', action='store_true', default=False,
                            dest='disable_check_update', help='disable online Glances version ckeck')
257 258 259
        parser.add_argument('--strftime', dest='strftime_format', default='',
                            help='strftime format string for displaying current date in standalone mode')

A
Alessio Sergi 已提交
260 261 262 263 264 265
        return parser

    def parse_args(self):
        """Parse command line arguments."""
        args = self.init_args().parse_args()

266
        # Load the configuration file, if it exists
A
Alessio Sergi 已提交
267 268
        self.config = Config(args.conf_file)

N
Nicolas Hennion 已提交
269 270 271 272
        # Debug mode
        if args.debug:
            from logging import DEBUG
            logger.setLevel(DEBUG)
273 274 275
        else:
            from warnings import simplefilter
            simplefilter("ignore")
N
Nicolas Hennion 已提交
276

277
        # Plugins disable/enable
278 279 280 281 282 283 284
        # Allow users to disable plugins from the glances.conf (issue #1378)
        for s in self.config.sections():
            if self.config.has_section(s) \
               and (self.config.get_bool_value(s, 'disable', False)):
                disable(args, s)
                logger.debug('{} disabled by the configuration file'.format(s))
        # The configuration key can be overwrite from the command line
285 286 287
        if args.disable_plugin is not None:
            for p in args.disable_plugin.split(','):
                disable(args, p)
288 289 290
        if args.enable_plugin is not None:
            for p in args.enable_plugin.split(','):
                enable(args, p)
291

292 293 294 295 296
        # Exporters activation
        if args.export is not None:
            for p in args.export.split(','):
                setattr(args, 'export_' + p, True)

297
        # Client/server Port
298 299 300 301
        if args.port is None:
            if args.webserver:
                args.port = self.web_server_port
            else:
302
                args.port = self.server_port
N
nicolargo 已提交
303
        # Port in the -c URI #996
304 305
        if args.client is not None:
            args.client, args.port = (x if x else y for (x, y) in zip(args.client.partition(':')[::2], (args.client, args.port)))
306

307 308 309 310
        # Autodiscover
        if args.disable_autodiscover:
            logger.info("Auto discover mode is disabled")

311
        # In web server mode
A
Alessio Sergi 已提交
312
        if args.webserver:
313
            args.process_short_name = True
N
Nicolas Hennion 已提交
314 315

        # Server or client login/password
316 317 318 319 320
        if args.username_prompt:
            # Every username needs a password
            args.password_prompt = True
            # Prompt username
            if args.server:
N
nicolargo 已提交
321 322
                args.username = self.__get_username(
                    description='Define the Glances server username: ')
323
            elif args.webserver:
N
nicolargo 已提交
324 325
                args.username = self.__get_username(
                    description='Define the Glances webserver username: ')
326
            elif args.client:
N
nicolargo 已提交
327 328
                args.username = self.__get_username(
                    description='Enter the Glances server username: ')
329
        else:
330 331 332 333 334 335
            if args.username_used:
                # A username has been set using the -u option ?
                args.username = args.username_used
            else:
                # Default user name is 'glances'
                args.username = self.username
336

337
        if args.password_prompt or args.username_used:
N
Nicolargo 已提交
338
            # Interactive or file password
A
Alessio Sergi 已提交
339
            if args.server:
N
Nicolas Hennion 已提交
340
                args.password = self.__get_password(
N
nicolargo 已提交
341 342
                    description='Define the Glances server password ({} username): '.format(
                        args.username),
343 344
                    confirm=True,
                    username=args.username)
345 346
            elif args.webserver:
                args.password = self.__get_password(
N
nicolargo 已提交
347 348
                    description='Define the Glances webserver password ({} username): '.format(
                        args.username),
349 350
                    confirm=True,
                    username=args.username)
A
Alessio Sergi 已提交
351
            elif args.client:
N
Nicolas Hennion 已提交
352
                args.password = self.__get_password(
N
nicolargo 已提交
353 354
                    description='Enter the Glances server password ({} username): '.format(
                        args.username),
355 356
                    clear=True,
                    username=args.username)
N
Nicolas Hennion 已提交
357 358 359 360
        else:
            # Default is no password
            args.password = self.password

A
Alessio Sergi 已提交
361 362 363 364 365 366 367
        # By default help is hidden
        args.help_tag = False

        # Display Rx and Tx, not the sum for the network
        args.network_sum = False
        args.network_cumul = False

368 369 370 371
        # Manage light mode
        if args.enable_light:
            logger.info("Light mode is on")
            args.disable_left_sidebar = True
372 373 374 375
            disable(args, 'process')
            disable(args, 'alert')
            disable(args, 'amps')
            disable(args, 'docker')
376

377 378
        # Manage full quicklook option
        if args.full_quicklook:
379 380 381 382 383 384
            logger.info("Full quicklook mode")
            enable(args, 'quicklook')
            disable(args, 'cpu')
            disable(args, 'mem')
            disable(args, 'memswap')
            enable(args, 'load')
385 386 387 388

        # Manage disable_top option
        if args.disable_top:
            logger.info("Disable top menu")
389 390 391 392 393
            disable(args, 'quicklook')
            disable(args, 'cpu')
            disable(args, 'mem')
            disable(args, 'memswap')
            disable(args, 'load')
394

395 396 397 398
        # Init the generate_graph tag
        # Should be set to True to generate graphs
        args.generate_graph = False

399 400 401
        # Control parameter and exit if it is not OK
        self.args = args

402
        # Export is only available in standalone or client mode (issue #614)
403
        export_tag = self.args.export is not None and any(self.args.export)
404 405 406
        if WINDOWS and export_tag:
            # On Windows, export is possible but only in quiet mode
            # See issue #1038
407
            logger.info("On Windows OS, export disable the Web interface")
408 409
            self.args.quiet = True
            self.args.webserver = False
410 411
        elif not (self.is_standalone() or self.is_client()) and export_tag:
            logger.critical("Export is only available in standalone or client mode")
412 413
            sys.exit(2)

414
        # Filter is only available in standalone mode
415
        if args.process_filter is not None and not self.is_standalone():
N
nicolargo 已提交
416 417
            logger.critical(
                "Process filter is only available in standalone mode")
418 419
            sys.exit(2)

420
        # Disable HDDTemp if sensors are disabled
421 422
        if getattr(args, 'disable_sensors', False):
            disable(args, 'hddtemp')
423 424
            logger.debug("Sensors and HDDTemp are disabled")

425 426 427 428 429 430 431
        # Let the plugins known the Glances mode
        self.args.is_standalone = self.is_standalone()
        self.args.is_client = self.is_client()
        self.args.is_client_browser = self.is_client_browser()
        self.args.is_server = self.is_server()
        self.args.is_webserver = self.is_webserver()

N
Nicolas Hennion 已提交
432
        return args
N
Nicolas Hennion 已提交
433

N
Nicolas Hennion 已提交
434
    def is_standalone(self):
A
PEP 257  
Alessio Sergi 已提交
435
        """Return True if Glances is running in standalone mode."""
436 437 438 439
        return (not self.args.client and
                not self.args.browser and
                not self.args.server and
                not self.args.webserver)
N
Nicolas Hennion 已提交
440 441

    def is_client(self):
A
PEP 257  
Alessio Sergi 已提交
442
        """Return True if Glances is running in client mode."""
443
        return (self.args.client or self.args.browser) and not self.args.server
444

445 446
    def is_client_browser(self):
        """Return True if Glances is running in client browser mode."""
447
        return self.args.browser and not self.args.server
N
Nicolas Hennion 已提交
448 449

    def is_server(self):
A
PEP 257  
Alessio Sergi 已提交
450
        """Return True if Glances is running in server mode."""
451
        return not self.args.client and self.args.server
N
Nicolas Hennion 已提交
452

453
    def is_webserver(self):
A
PEP 257  
Alessio Sergi 已提交
454
        """Return True if Glances is running in Web server mode."""
455
        return not self.args.client and self.args.webserver
N
Nicolas Hennion 已提交
456

457
    def get_config(self):
A
PEP 257  
Alessio Sergi 已提交
458
        """Return configuration file object."""
459
        return self.config
N
Nicolas Hennion 已提交
460 461

    def get_args(self):
A
PEP 257  
Alessio Sergi 已提交
462
        """Return the arguments."""
A
Alessio Sergi 已提交
463
        return self.args
464 465 466 467 468 469

    def get_mode(self):
        """Return the mode."""
        return self.mode

    def __get_username(self, description=''):
470
        """Read an username from the command line."""
471 472
        return input(description)

473 474
    def __get_password(self, description='',
                       confirm=False, clear=False, username='glances'):
475 476 477 478 479 480 481 482
        """Read a password from the command line.

        - if confirm = True, with confirmation
        - if clear = True, plain (clear password)
        """
        from glances.password import GlancesPassword
        password = GlancesPassword(username=username)
        return password.get_password(description, confirm, clear)