mirror of
https://github.com/kristoferssolo/School.git
synced 2025-10-21 20:10:38 +00:00
170 lines
5.7 KiB
Python
170 lines
5.7 KiB
Python
"""
|
|
This module contains the command line interface for snakeviz.
|
|
|
|
"""
|
|
from __future__ import print_function
|
|
|
|
import argparse
|
|
import os
|
|
import random
|
|
import socket
|
|
import sys
|
|
import threading
|
|
import webbrowser
|
|
from pstats import Stats
|
|
|
|
try:
|
|
from urllib.parse import quote
|
|
except ImportError:
|
|
from urllib import quote
|
|
|
|
from . import version
|
|
|
|
|
|
# As seen in IPython:
|
|
# https://github.com/ipython/ipython/blob/8be7f9abd97eafb493817371d70101d28640919c/IPython/html/notebookapp.py
|
|
# See the IPython license at:
|
|
# https://github.com/ipython/ipython/blob/master/COPYING.rst.
|
|
def random_ports(port, n):
|
|
"""Generate a list of n random ports near the given port.
|
|
The first 5 ports will be sequential, and the remaining n-5 will be
|
|
randomly selected in the range [port-2*n, port+2*n].
|
|
"""
|
|
for i in range(min(5, n)):
|
|
yield port + i
|
|
for i in range(n-5):
|
|
yield max(1, port + random.randint(-2*n, 2*n))
|
|
|
|
|
|
class SVArgumentParser(argparse.ArgumentParser):
|
|
def error(self, message):
|
|
message = message + '\n\n' + self.format_help()
|
|
args = {'prog': self.prog, 'message': message}
|
|
self.exit(2, '%(prog)s: error: %(message)s' % args)
|
|
|
|
|
|
def build_parser():
|
|
parser = SVArgumentParser(
|
|
description='Start SnakeViz to view a Python profile.')
|
|
|
|
parser.add_argument('filename', help='Python profile to view')
|
|
|
|
parser.add_argument('-v', '--version', action='version',
|
|
version=('%(prog)s ' + version.version))
|
|
|
|
parser.add_argument('-H', '--hostname', metavar='ADDR', default='127.0.0.1',
|
|
help='hostname to bind to (default: %(default)s)')
|
|
|
|
parser.add_argument('-p', '--port', type=int, metavar='PORT', default=8080,
|
|
help='port to bind to; if this port is already in use a '
|
|
'free port will be selected automatically '
|
|
'(default: %(default)s)')
|
|
|
|
parser.add_argument('-b', '--browser', metavar='BROWSER_PATH',
|
|
help='name of webbrowser to launch as described in '
|
|
'the documentation of Python\'s webbrowser module: '
|
|
'https://docs.python.org/3/library/webbrowser.html')
|
|
|
|
parser.add_argument('-s', '--server', action="store_true", default=False,
|
|
help='start SnakeViz in server-only mode--'
|
|
'no attempt will be made to open a browser')
|
|
|
|
return parser
|
|
|
|
|
|
def main(argv=None):
|
|
parser = build_parser()
|
|
args = parser.parse_args(argv)
|
|
|
|
if args.browser and args.server:
|
|
parser.error("options --browser and --server are mutually exclusive")
|
|
|
|
filename = os.path.abspath(args.filename)
|
|
if not os.path.exists(filename):
|
|
parser.error('the path %s does not exist' % filename)
|
|
|
|
if not os.path.isdir(filename):
|
|
try:
|
|
open(filename)
|
|
except IOError as e:
|
|
parser.error('the file %s could not be opened: %s'
|
|
% (filename, str(e)))
|
|
|
|
try:
|
|
Stats(filename)
|
|
except Exception:
|
|
parser.error(('The file %s is not a valid profile. ' % filename) +
|
|
'Generate profiles using: \n\n'
|
|
'\tpython -m cProfile -o my_program.prof my_program.py\n\n'
|
|
'Note that snakeviz must be run under the same '
|
|
'version of Python as was used to create the profile.\n')
|
|
|
|
filename = quote(filename, safe='')
|
|
|
|
hostname = args.hostname
|
|
port = args.port
|
|
|
|
if not 0 <= port <= 65535:
|
|
parser.error('invalid port number %d: use a port between 0 and 65535'
|
|
% port)
|
|
|
|
# Before starting tornado set the eventloop policy for windows and python 3.8 compatibility
|
|
# https://github.com/tornadoweb/tornado/issues/2608
|
|
if sys.platform == 'win32' and sys.version_info[:2] == (3, 8):
|
|
import asyncio
|
|
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
|
|
|
|
# Go ahead and import the tornado app and start it; we do an inline import
|
|
# here to avoid the extra overhead when just running the cli for --help and
|
|
# the like
|
|
|
|
from .main import app
|
|
import tornado.ioloop
|
|
|
|
# As seen in IPython:
|
|
# https://github.com/ipython/ipython/blob/8be7f9abd97eafb493817371d70101d28640919c/IPython/html/notebookapp.py
|
|
# See the IPython license at:
|
|
# https://github.com/ipython/ipython/blob/master/COPYING.rst.
|
|
for p in random_ports(port, 10):
|
|
try:
|
|
app.listen(p, address=hostname)
|
|
except socket.error as e:
|
|
print('Port {0} in use, trying another.'.format(p))
|
|
else:
|
|
port = p
|
|
break
|
|
else:
|
|
print('No available port found.')
|
|
return 1
|
|
|
|
url = "http://{0}:{1}/snakeviz/{2}".format(hostname, port, filename)
|
|
print(('snakeviz web server started on %s:%d; enter Ctrl-C to exit' %
|
|
(hostname, port)))
|
|
print(url)
|
|
|
|
if not args.server:
|
|
try:
|
|
browser = webbrowser.get(args.browser)
|
|
except webbrowser.Error as e:
|
|
parser.error('no web browser found: %s' % e)
|
|
|
|
# Launch the browser in a separate thread to avoid blocking the
|
|
# ioloop from starting
|
|
def bt():
|
|
browser.open(url, new=2)
|
|
threading.Thread(target=bt).start()
|
|
|
|
try:
|
|
tornado.ioloop.IOLoop.instance().start()
|
|
except KeyboardInterrupt:
|
|
# TODO: Cheap KeyboardInterrupt handler for now; iPython has some nicer
|
|
# stuff for handling SIGINT and SIGTERM that might be worth borrowing
|
|
tornado.ioloop.IOLoop.instance().stop()
|
|
print('\nBye!')
|
|
|
|
return 0
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main())
|