diff --git a/deluge/common.py b/deluge/common.py index b56b383b6..5910b2d40 100644 --- a/deluge/common.py +++ b/deluge/common.py @@ -726,7 +726,6 @@ class VersionSplit(object): """ def __init__(self, ver): - import re version_re = re.compile(r''' ^ (?P\d+\.\d+) # minimum 'N.N' @@ -993,3 +992,40 @@ def unicode_argv(): encoding = encoding or "utf-8" return [arg.decode(encoding) for arg in sys.argv] + + +def run_profiled(func, *args, **kwargs): + """ + Profile a function with cProfile + + Args: + func (func): The function to profile + *args (tuple): The arguments to pass to the function + do_profile (bool, optional): If profiling should be performed. Defaults to True. + output_file (str, optional): Filename to save profile results. If None, print to stdout. + Defaults to None. + """ + if kwargs.get("do_profile", True) is not False: + import cProfile + profiler = cProfile.Profile() + + def on_shutdown(): + output_file = kwargs.get("output_file", None) + if output_file: + profiler.dump_stats(output_file) + log.info("Profile stats saved to %s", output_file) + print "Profile stats saved to %s" % output_file + else: + import pstats + import StringIO + strio = StringIO.StringIO() + ps = pstats.Stats(profiler, stream=strio).sort_stats('cumulative') + ps.print_stats() + print strio.getvalue() + + try: + return profiler.runcall(func, *args) + finally: + on_shutdown() + else: + return func(*args) diff --git a/deluge/main.py b/deluge/main.py index 909b47173..d43dc4e4a 100644 --- a/deluge/main.py +++ b/deluge/main.py @@ -214,19 +214,5 @@ def start_daemon(skip_start=False): if options.pidfile: os.remove(options.pidfile) - if options.profile: - import cProfile - profiler = cProfile.Profile() - profile_output = deluge.configmanager.get_config_dir("deluged.profile") - - # Twisted catches signals to terminate - def save_profile_stats(): - profiler.dump_stats(profile_output) - print("Profile stats saved to %s" % profile_output) - - from twisted.internet import reactor - reactor.addSystemEventTrigger("before", "shutdown", save_profile_stats) - print("Running with profiler...") - profiler.runcall(run_daemon, options) - else: - return run_daemon(options) + return deluge.common.run_profiled(run_daemon, options, output_file=options.profile, + do_profile=options.profile) diff --git a/deluge/ui/baseargparser.py b/deluge/ui/baseargparser.py index cdca4e973..a85184ddb 100644 --- a/deluge/ui/baseargparser.py +++ b/deluge/ui/baseargparser.py @@ -106,6 +106,9 @@ class BaseArgParser(argparse.ArgumentParser): help="Sets the log level to 'none', this is the same as `-L none`") self.group.add_argument("-r", "--rotate-logs", action="store_true", default=False, help="Rotate logfiles.") + self.group.add_argument("--profile", metavar="", action="store", nargs="?", default=False, + help="Profile %(prog)s with cProfile. Prints results to stdout" + "unless a filename is specififed.") self.group.add_argument("-h", "--help", action=HelpAction, help='Show this help message and exit') def parse_args(self, *args): diff --git a/deluge/ui/console/console.py b/deluge/ui/console/console.py index a27298e6d..955a8809b 100644 --- a/deluge/ui/console/console.py +++ b/deluge/ui/console/console.py @@ -8,12 +8,15 @@ # See LICENSE for more details. # +import logging import os +import deluge.common from deluge.ui.baseargparser import DelugeTextHelpFormatter from deluge.ui.console import UI_PATH from deluge.ui.ui import UI +log = logging.getLogger(__name__) # # Note: Cannot import from console.main here because it imports the twisted reactor. @@ -76,4 +79,13 @@ class Console(UI): def start(self, args=None): super(Console, self).start(args) from deluge.ui.console.main import ConsoleUI # import here because (see top) - ConsoleUI(self.options, self.console_cmds) + + def run(options): + try: + ConsoleUI(self.options, self.console_cmds) + except Exception as ex: + log.exception(ex) + raise + + deluge.common.run_profiled(run, self.options, output_file=self.options.profile, + do_profile=self.options.profile) diff --git a/deluge/ui/console/main.py b/deluge/ui/console/main.py index 128b709be..99b80d41e 100644 --- a/deluge/ui/console/main.py +++ b/deluge/ui/console/main.py @@ -184,7 +184,7 @@ class ConsoleUI(component.Component): curses.wrapper(self.run) elif self.interactive and deluge.common.windows_check(): print("""\nDeluge-console does not run in interactive mode on Windows. \n -Please use commands from the command line, eg:\n +Please use commands from the command line, e.g.:\n deluge-console.exe help deluge-console.exe info deluge-console.exe "add --help" diff --git a/deluge/ui/gtkui/gtkui.py b/deluge/ui/gtkui/gtkui.py index 21695e241..3ceace614 100644 --- a/deluge/ui/gtkui/gtkui.py +++ b/deluge/ui/gtkui/gtkui.py @@ -146,9 +146,17 @@ class Gtk(UI): " to a currently running Deluge GTK instance") def start(self, args=None): - from gtkui import GtkUI super(Gtk, self).start(args) - GtkUI(self.options) + + def run(options): + try: + GtkUI(options) + except Exception as ex: + log.exception(ex) + raise + + deluge.common.run_profiled(run, self.options, output_file=self.options.profile, + do_profile=self.options.profile) class GtkUI(object): diff --git a/deluge/ui/web/web.py b/deluge/ui/web/web.py index f0a07bdb3..2cc556cc8 100644 --- a/deluge/ui/web/web.py +++ b/deluge/ui/web/web.py @@ -9,17 +9,14 @@ from __future__ import print_function +import logging import os -from deluge.common import osx_check, windows_check +from deluge.common import osx_check, run_profiled, windows_check from deluge.configmanager import get_config_dir from deluge.ui.ui import UI - -# -# Note: Cannot import twisted.internet.reactor because Web is imported from -# from web/__init__.py loaded by the script entry points defined in setup.py -# +log = logging.getLogger(__name__) class WebUI(object): @@ -56,8 +53,6 @@ class Web(UI): help="Binds the webserver to a specific IP address") group.add_argument("-p", "--port", metavar="", type=int, action="store", default=None, help="Sets the port to be used for the webserver") - group.add_argument("--profile", action="store_true", default=False, - help="Profile the web server code") try: import OpenSSL assert OpenSSL.__version__ @@ -123,26 +118,14 @@ class Web(UI): self.server.https = self.options.ssl - def run_server(): - self.server.install_signal_handlers() - self.server.start() - - if self.options.profile: - import cProfile - profiler = cProfile.Profile() - profile_output = get_config_dir("delugeweb.profile") - - # Twisted catches signals to terminate - def save_profile_stats(): - profiler.dump_stats(profile_output) - print("Profile stats saved to %s" % profile_output) - - from twisted.internet import reactor # import here because (see top) - reactor.addSystemEventTrigger("before", "shutdown", save_profile_stats) - print("Running with profiler...") - profiler.runcall(run_server) - else: - run_server() + def run(): + try: + self.server.install_signal_handlers() + self.server.start() + except Exception as ex: + log.exception(ex) + raise + run_profiled(run, output_file=self.options.profile, do_profile=self.options.profile) def start():