#!/usr/bin/env python

import os.path
import sys
import gobject

from pycallgraph import PyCallGraph, Config
from pycallgraph.output import GraphvizOutput

#network bits:
net = ['xpra.net.protocol.*',
       'xpra.net.protocol.Protocol.start',
       'xpra.net.bytestreams.*',
       'xpra.net.bencode.*',
       'xpra.net.rencode.*',
       'xpra.net.compression.*',
       'xpra.net.packet_encoding.*',
       'xpra.net.header.*',
       'xpra.net.mmap_pipe.*',
       'xpra.net.crypto.*',
       'xpra.net.net_util.*',
       'Crypto*',
       'xpra.*server*.process_packet',
       'xpra.*server*.next_packet',
       'socket.*',
       '*._write_thread_loop',
       '*._read_thread_loop',
       '*._read_parse_thread_loop',
       #mdns:
       'xpra.net.avahi*',
       ]
x11 = ['xpra.x11.*',
       'xpra.gtk_common.error.*']
damage = [
          #'xpra.server.source.*',
          #'xpra.server.*._damage',
          'xpra.server.region*',
          'xpra.server.window*_source.*',
          'xpra.server.video_subregion.*',
          'xpra.server.batch_delay_calculator.*',
          'xpra.gtk_common.pixbuf_to_rgb.*',
          'xpra.deque.*',
          'xpra.server.source_stats.*',
          'xpra.server.window_stats.*']
codecs = ['xpra.codecs.*.<module>',
          'xpra.codecs.x264.*', 'xpra.codecs.vpx.*', 'xpra.codecs.webp.*',
          'xpra.codecs.xor.*',
          "PIL.*",
          ]
mouse = ['xpra.*server*._process_pointer_position',
         'xpra.*server*._process_button_action',
         'xpra.*server*._process_mouse_common']
keyboard = [
       'xpra.gtk_common.keys.*',
       'xpra.keyboard.*',
       'xpra.x11.xkbhelper.*', 'xpra.x11.gtk_x11.keys.*',
       'xpra.x11.server_keyboard_config.*',
       'xpra.server.source.ServerSource.make_keymask_match',
       'xpra.server.*._keys_changed',       #in both XpraServer and Source
       'xpra.server.*._process_key*',       #process_key_action, process_key_repeat
       'xpra.server.*._key_repeat*',        #_key_repeat, _key_repeat_timeout
       'xpra.server.*._clear_keys_pressed',
       'xpra.server.*._handle_key',
       'xpra.server.*.get_keycode',         #in both ServerBase and Source
       'xpra.client.*.get*_keymap*',
       'xpra.client.*.*_key_action',        #send, handle, process, parse
       'xpra.client.*.*key_press_event',
       'xpra.client.*.update_hash',
       'xpra.client.*.hashadd',
       'xpra.*.*Keyboard*',         #Keyboard and KeyboardConfig classes, KeyboardHelper
       'xpra.*.*KeyboardHelper.update',
       'xpra.*.*KeyEvent',
       'xpra.*._key_repeat',
       'xpra.*.clear_repeat',
       'xpra.*.key_handled*',
       'xpra.*.is_modifier',
       'xpra.server.*.press',
       'xpra.server.*.unpress',
       ]
cursor = [
          'xpra.*.do_xpra_cursor_event',
          'xpra.*._process_cursor',
          'xpra.*.set_windows_cursor',
          ]
bell = ['xpra.x11.gtk_x11.window.WindowModel.do_xpra_xkb_event',
        'xpra.x11.gtk_x11.wm.Wm.bell_event',
        'xpra.x11.gtk_x11.wm.Wm.do_bell_event',
        'xpra.server.XpraServer._bell_signaled',
        'xpra.server.source.ServerSource.bell']
misc = ['xpra.dotxpra.*', 'xpra.x11.bindings.wait_for_server.*',
        'xpra.scripts.*', 'subprocess.*',
        'xpra.log*',
        'xpra.gtk_common.gobject_compat.*',
        'xpra.x11.gtk_x11.tray.*',
        'xpra.codecs.version_info.*',
        'xpra.version_util.*',
        'xpra.gtk_common.gtk_util.add_gtk_version_info',
        'xpra.build_info.*']

xsettings = ['xpra.platform.xposix.xroot_props.*', 'xpra.platform.xposix.xsettings.*', 'xpra.x11.xsettings_prop.*']
clipboard = ['xpra.clipboard.*',
             'xpra.client.*.*clipboard_helper',
             'xpra.gtk_common.gdk_atoms.*',
             'xpra.gtk_common.nested_main.*',
             'xpra.x11.gtk_x11.selection.*',
             'xpra.*.ClientExtras.setup_clipboard_helper']
sound = ['xpra.sound.*',
         'xpra.*server*.*sound*',
         'xpra.*.start*sound*',        #start_receiving_sound, start_sound_sink
         'gst.*', 'pygst.*',
         'xpra.*.sound*changed',
         'xpra.*.sink_ready']
gl = ['xpra.client.gl.gl_client_window.*', 'xpra.client.gl.gl_colorspace_conversions.*', 'xpra.client.gl.gl_window_backing.*']

logging = [
       'logging.*',
       'xpra.log.*']

std = ['pycallgraph.*',
       'traceback.*', 'linecache.*',
       '_weakrefset.*', 'weakref.*',
       'DLFCN.*',
       'fnmatch.*',     #pycallgraph itself!
       'pyopencl.*',    #too big
       'pycuda.*',      #too big
       'OpenGL.*',      #far too big!
       'numpy.*',       #also too big!
       #Pillow pollution (I think - most of it):
       'PIL.*',
       'Image*',
       '_ImageCrop',
       '_imaging_not_installed',
       'PSFile*',
       'IcnsFile*',
       'ModeDescriptor',
       'PngStream',
       'PngInfo',
       'Mismatch',
       'ChunkStream',
       'Legendre',
       'Chebyshev',
       'Laguerre',
       'Hermite*',
       'Parser',
       'Match',
       'IcoFile',
       'Polynomial',
       'FixTk',
       'DecompressionBombWarning',
       'BitStream',
       'register_extension',
       'register_mime',
       'register_save',
       'register_open',
       'build_prototype_image',
       'Ole*',
       '_Ole*',
       #most of these are not ours:
       'pprint.*',
       'functools.*',
       'pytools.*',
       'decorator.*',
       'tempfile.*',
       'shutil.*',
       'difflib.*',      #from numpy?
       '*ImageFile*',    #no need for data on individual PIL formats
       'ImagePalette',
       'unittest.*',     #why does this even get imported?
       'numbers.*',
       'contextlib.*',
       'collections.*',
       'inspect.*',
       'pkg_resources.*',
       'sysconfig*',
       'pkgutil.*',
       'zipfile.*',
       'cffi*',
       'gi.*',           #GTK3!
       'urlparse.*',
       'abc.*',
       'string.*',
       'StringIO.<module>',
       'StringIO.StringIO',
       'shlex.*',
       'pickle.*',
       're.*', 'sre_parse.*', 'sre_compile.*',
       'atexit.*', 'warnings.*',
       'getpass.*',
       'posixpath.*', 'genericpath.*', 'stat.*',
       'threading.*',
       'encodings.*',
       'optparse.*', 'gettext.*', 'locale.*', 'codecs.*']

libs = ['gobject.*', 'gtk.*', 'uuid.*', 'pygtk.*', 'gio.*', 'cairo.*',
        'os.getenv', 'os.environ.*', 'os._Environ.*', 'UserDict.*', 'platform.*', 'string.split',
        'dbus.*',
        'yaml.*',
        'libxml2.*', "xml.*", 'StartElement', 'EndElement',    #used by gst..
        'ctypes.*', 'hmac.*']

one_offs = ['__main__',
            '__bootstrap__',
            '<module>',
            'xpra.<module>',
            'xpra.*.<module>',
            'Queue.<module>', 'Queue,_init', 'Queue.Full', 'Queue.Queue', 'Queue.Empty', 'Queue.LifoQueue', 'Queue.PriorityQueue',
            'xpra.*.*init_aliases',
            'xpra.*server*.*ServerBase',
            'xpra.*server*.__init__',
            'xpra.*server*.init',
            'xpra.*server*.init_auth',
            'xpra.*server*.init_encodings',
            'xpra.*server*.init_sockets',
            'xpra.*server*.init_when_ready',
            'xpra.*server*.x11_init',
            'xpra.*server*.init_x11_atoms',
            'xpra.*server*.init_clipboard',
            'xpra.*server*.init_keyboard',
            'xpra.*server*.init_notification_forwarder',
            'xpra.*server*.init_packet_handlers',
            'xpra.*server*.add_encodings',
            'xpra.*server*.watch_keymap_changes',
            'xpra.*server*.reenable_keymap_changes',
            'xpra.*server*.load_existing_windows',
            'xpra.*server*.get_root_window_size',
            'xpra.*server*.get_max_screen_size',
            'xpra.*server*.get_default_cursor',
            'xpra.*server*.add_listen_socket',
            'xpra.*server*.get_server_mode',
            'xpra.*server*.do_check',
            'xpra.*server*.clipboard*_check',
            'xpra.*server*.print_ready',
            'xpra.*server*.start_ready_callbacks',
            'xpra.*server*.init_uuid',
            'xpra.*server*.get_uuid',
            'xpra.*server*.save_uuid',
            'xpra.*server*.run',
            'xpra.*server*.do_run',
            'xpra.*server*._process_desktop_size',
            'xpra.*server*._process_shutdown_server',
            'xpra.server.window*source.Window*Source',
            'xpra.server.window*source.envint',
            'xpra.util.is_unity',
            'xpra.os_util.rel',
            'xpra.os_util.platform_name',
            'xpra.os_util.get_hex_uuid',
            'xpra.os_util.load_binary_file',
            'xpra.os_util.set_*_name',
            'xpra.platform.*set_application_name',
            'xpra.*.get*_info',
            'xpra.server.DesktopManager.__init__',
            'xpra.codecs.codec_constants.*',
            'xpra.codecs.loader.*',
            'xpra.codecs.video_helper.<lambda>',        #sorting
            'xpra.codecs.video_helper.try_import_modules',
            'xpra.codecs.video_helper.get_*_name',
            'xpra.codecs.video_helper.has_codec_module',
            'xpra.codecs.video_helper.VideoHelper.*init*',
            'xpra.codecs.video_helper.VideoHelper',
            'xpra.codecs.video_helper.VideoHelper.set_modules',
            'xpra.codecs.video_helper.VideoHelper.add_*',
            'xpra.codecs.video_helper.VideoPipelineHelper',
            'xpra.codecs.video_helper.VideoPipelineHelper.may_init',
            'xpra.codecs.video_helper.VideoPipelineHelper.init_*',
            'xpra.server.codec_constants.codec_spec.__init__',
            'xpra.daemon_thread.*',
            'threading.Thread.daemon', 'threading._MainThread.daemon',
            'threading._MainThread.name',
            'threading._newname',
            'threading.Thread.setDaemon', 'threading.Thread.set_daemon', 'threading.Thread._set_daemon',
            'threading.Thread.__init__',
            'threading.Condition.*', 'threading.Event.*',
            'xpra.gtk_common.quit.*',           #gtk_main_quit_forever, gtk_main_quit_really, gtk_main_quit_on_fatal_exceptions_enable
            'xpra.x11.gtk_x11.wm.Wm.__init__',
            'xpra.x11.gtk_x11.wm.Wm.__setup_ewmh_window',
            'xpra.x11.gtk_x11.wm.Wm.enableCursors',
            'xpra.gtk_common.*.n_arg_signal',
            'xpra.x11.gtk_x11.error._ErrorManager.__init__',
            #dotxpra finding sockets:
            'glob.*',
            #client bits:
            'xpra.client.client_base.b',        #unavoidable
            #class init:
            'xpra.client.*.*XpraClient',
            'xpra.client.*.*ClientBase',
            'xpra.client.*.*WidgetBase',
            'xpra.client.*.*Window*Base',       #*WindowBase, *WindowBackingBase
            'xpra.client.*.*ClientWindow',      #Custom*, Border*, ..
            'xpra.client.*.*Backing',           #PixmapBacking, etc
            'xpra.client.*.*CommandConnectClient',
            'xpra.client.*.ClientSource',
            #instance init:
            'xpra.client.*.__init__',           #remove from here?
            'xpra.client.*.defaults_init',
            'xpra.client.*.init',
            'xpra.client.*.init_ui',
            'xpra.client.*.glib_init',
            'xpra.client.*.gobject_init',
            'xpra.client.*.install_signal_handlers',
            'xpra.client.*.setup_connection',
            'xpra.client.*.get_scheduler',
            'xpra.client.*.up',
            'xpra.client.*.client_type',
            'xpra.client.*.*get*encodings',
            'xpra.client.*.*get*window_layouts',
            'xpra.client.*.make_keyboard_helper',
            'xpra.client.*.make_notifier',
            'xpra.client.*.make_instance',
            'xpra.client.*.get*_classes',
            'xpra.client.*.parse_border',
            'xpra.client.*.register*toggled',
            'xpra.client.*.*toggled',
            'xpra.client.*.*_notify',
            'xpra.client.*.process_ui_capabilities',
            'xpra.client.*._startup_complete',
            'xpra.client.*.run',
            'xpra.client.*.gtk*_main',
            'xpra.client.*.verify_connected',
            'xpra.gtk_common.*.gtk*main',
            'xpra.platform.platform_import',
            'xpra.platform.*.get*_classes',
            'xpra.*.ui_thread_watcher.*get_UI_watcher',  #UI_thread_watcher, get_UI_watcher
            'xpra.*.ui_thread_watcher.*.UI_thread_watcher',
            'xpra.*.ui_thread_watcher.*.UI_thread_watcher.__init__',
            'xpra.*.ui_thread_watcher.*.add*callback',
            'xpra.*.ui_thread_watcher.*.start',
            'xpra.*.ui_thread_watcher.*.stop',
            'xpra.util.updict',
            'xpra.platform.*.add_client_options', 'xpra.platform.*.add_*_option',
            'socket._socketobject.meth',
            'xpra.*.*hello*',
            'xpra.util.typedict.*',
            'xpra.*.GetClipboard',
            'xpra.*.get*_uuid',
            'xpra.*.uupdate',
            'xpra.*.get_machine_id',
            'xpra.*.init_packet_handlers',
            'xpra.*.get_screen_sizes',
            'xpra.*.get_root_size',
            'xpra.*.parse_shortcuts',
            'xpra.*.ready',
            'xpra.*.do_ready',
            'xpra.*.setup_pa_audio_tagging',
            'xpra.*.setup_xprop_xsettings',
            'xpra.net.protocol.Protocol.__init__',
            'xpra.*.make_uuid',
            'xpra.cursor_names.*',
            'xpra.platform.*.ClientExtras',
            'xpra.platform.*.ClientExtras.__init__',
            'xpra.platform.*.ClientExtras.setup*',
            'xpra.platform.init',
            'xpra.platform.*.init',
            'xpra.platform.*.do_init',
            'xpra.platform.*set_prgname',
            'xpra.platform.*get_username',
            'xpra.platform.*get_name',
            'xpra.platform.*._get_pwd',
            'xpra.platform.*clean',
            'xpra.platform.*clean',
            'xpra.platform.paths*.get*dir',
            #screen logging:
            'xpra.util.*log_screen_sizes',
            'xpra.util.prettify_plug_name',
            #GL init:
            'xpra.client.*.init_opengl',
            'xpra.*.gl_check*',
            #keyboard stuff that we only do once:
            'xpra.*._do_keys_changed',
            'xpra.*.query_xkbmap',
            'xpra.*.grok_modifier_map',
            'xpra.*.get_keyboard_repeat',
            'xpra.*.set_keyboard_repeat',
            'xpra.*.get_x11_keymap',
            'xpra.*.get_gtk_keymap',
            'xpra.*.get_keymap_modifiers',
            'xpra.*.get_keymap_spec*',
            'xpra.*.get_layout_spec*',
            'xpra.*.exec_get_keyboard_data',
            'xpra.*.set_modifier_mappings',
            'xpra.*.update_modmap',
            'xpra.keyboard.layouts.*',
            'xpra.platform.*.update_modmap',
            #some network stuff only happens once:
            'xpra.*.set_max_packet_size',
            #exit stuff:
            'xpra.*._process_connection_lost',
            'xpra.*.warn_and_quit',
            'xpra.*.quit',
            'xpra.*.do_quit',
            'xpra.*.clean_quit',
            'xpra.*.quit_timer',
            'xpra.*.cleanup',
            'xpra.*.clean_mmap',
            'xpra.*.close_about',
            ]

dialogs = [
            #tray:
            'xpra.*.setup*_tray*',
            'xpra.*.make*_tray*',
            'xpra.*.add*_tray*',
            'xpra.*.get_tray*',
            'xpra.*.supports*_tray*',
            'xpra.*.*tray_geometry',
            'xpra.*statusicon*',
            'xpra.*is_ubuntu*',
            '*appindicator*',
            'xpra.*.client_tray*',
            'xpra.*.hide_tray',
            'xpra.client.tray_base.*',

            'xpra.*.get_icon*',
            'xpra.*.get_image*',
            'xpra.*.get_pixbuf*',
            'xpra.*.scaled_image*',
            'xpra.*.get_data_dir',
            'xpra.*.get_icons_dir',
            'xpra.*.setup_xprops',
            'xpra.*.setup_x11_bell',
            'xpra.*.supports_clipboard',
            #notifications:
            'xpra.client.notifications.*',
            'xpra.*.setup_dbusnotify',
            'xpra.*.can_notify',
            'xpra.*.gtk2_notifier.*',
            '*pynotify.*',
            #tray menu:
            'xpra.*.*tray_menu*',
            'xpra.*.supports_server',
            'xpra.*.setup_menu',
            'xpra.*.make_*submenu',
            'xpra.*.checkitem',
            'xpra.*.kbitem',
            'xpra.*.*menuitem',         #menuitem, make_*, handshake_*, enable*, activate*, close*, show*, may_enable*, set_*
            'xpra.*.set_*menu',
            'xpra.*.menu_deactivated',
            'xpra.*.CheckMenuItem',
            'xpra.*.keysort',
            'xpra.*.ClientExtras.popup_menu_workaround',
            'xpra.*.ClientExtras.setup*',
            'xpra.*.ClientExtras.*toggled',
            'xpra.*.ClientExtras.set_keyboard_sync_tooltip',
            'xpra.*.ClientExtras.*state',
            'xpra.*.ClientExtras.set_selected_layout',
            'xpra.*.ClientExtras.set_menu_title',
            'xpra.*.ClientExtras.get_image',
            'xpra.*.ClientExtras.get_pixbuf',
            'xpra.*.ClientExtras.get_icon_filename',
            'xpra.*.ClientExtras.set_window_icon',
            'xpra.*.set_tooltip_text',
            'webbrowser.*',
            #network related, but only happens rarely (user action or initial connection):
            'xpra.*.send_*_enabled',        #bell, cursors, deflate
            'xpra.*._process_set_deflate',
            #session info:
            'xpra.*.session_info',
            'xpra.*.session_info.*',
            'xpra.platform.graph.*',
            'xpra.*.TableBuilder*',
            ]

connection = ['xpra.*server*.send_hello',
              'xpra.*server*.make_hello',
              'xpra.*server*._get_desktop_size_capability',
              'xpra.*server*.*hello*',
              'xpra.*server*.parse_hello',
              'xpra.*server*.batch_value',
              'xpra.*server*.parse_batch_int',
              'xpra.net.protocol.Protocol.__str__',
              'xpra.net.protocol.Protocol.start',
              #handle connection:
              'socket._socketobject.accept',
              'socket._socketobject.__init__',
              'xpra.net.bytestreams.SocketConnection.__init__',
              'xpra.net.bytestreams.SocketConnection.__str__',
              'xpra.*server*._new_connection',
              'xpra.*server*.verify_connection_accepted',
              'xpra.*server*._process_hello',
              'xpra.*server*.sanity_checks',
              'xpra.*server*.get_max_screen_size',
              'xpra.*server*.set_best_screen_size',
              'xpra.*server*.set_screen_size',
              'xpra.*server*.send_updated_screen_size',
              'xpra.*server*.set_workarea',
              'xpra.*server*.calculate_workarea',
              'xpra.*server*._screen_size_changed',
              'xpra.*server*._process_set_deflate',
              'xpra.*server*.parse_encoding_caps',
              'xpra.*server*.set_keymap',
              'xpra.server.source.ServerSource.set_deflate',
              'xpra.server.source.ServerSource.parse_hello',
              'xpra.server.source.ServerSource.init_mmap',
              'xpra.server.source.ServerSource.keys_changed',
              'xpra.server.source.ServerSource.set_keymap',
              'xpra.server.source.ServerSource.updated_desktop_size',
              'xpra.server.source.ServerSource.set_screen_sizes',
              'xpra.server.source.ServerSource.set_encoding',
              'xpra.server.source.ServerSource.assign_keymap_options',
              'xpra.server.*.send_windows_and_cursors',
              'xpra.net.protocol.Protocol.set_compression_level',
              'xpra.net.protocol.Protocol.enable_rencode',
              'xpra.net.protocol.Protocol.do_start',
              #disconnection:
              'xpra.*server*.send_disconnect',
              'xpra.*server*._process_connection_lost',
              'xpra.*server*.cleanup_source',
              'xpra.*server*.disconnect_client',
              'xpra.*server*.no_more_clients',
              'xpra.net.protocol.Protocol.flush_then_close',
              'xpra.net.protocol.Protocol.send_now',
              'xpra.net.protocol.Protocol.close',
              'xpra.net.protocol.Protocol.clean',
              'xpra.net.protocol.Protocol.terminate_io_threads',
              'xpra.net.bytestreams.SocketConnection.close',
              'socket._socketobject.close']

ALL = std + x11 + damage + codecs + net + mouse + keyboard + cursor + bell + misc + xsettings + clipboard + sound + gl + logging + libs + one_offs + dialogs + connection

COMMON_THREAD_NAMES = ["write", "read", "parse", "format"]
SERVER_THREAD_NAMES = ["encode",
                       #only used in proxy
                       "server message queue",
                       #not handled yet: "Worker_Thread"
                       ]
CLIENT_THREAD_NAMES = ["draw", "UI thread polling"]
THREAD_NAMES = COMMON_THREAD_NAMES + SERVER_THREAD_NAMES + CLIENT_THREAD_NAMES
SETS = ["ALL", "std", "x11", "damage", "codecs", "net", "cursor", "mouse", "keyboard", "bell", "misc", "xsettings", "clipboard", "sound", "gl", "logging", "libs", "one_offs", "dialogs", "connection"]


def usage(msg=None):
    if msg:
        print(msg)
    cmd = os.path.basename(sys.argv[0])
    print("%s usage: -I include-expressions -i include-set -E exclude-expressions -e exclude-set -d DELAY -r RUNTIME -t thread-name -- XPRA ARGS" % sys.argv[0])
    print("The default thread is the main thread, other options are:")
    print(" - for both client and server: %s" % ", ".join(COMMON_THREAD_NAMES))
    print(" - server-only threads: %s" % ", ".join(SERVER_THREAD_NAMES))
    print(" - client-only threads: %s" % ", ".join(CLIENT_THREAD_NAMES))
    print("The include and exclude sets are defined as a coma seperated list of package groups.")
    print("The package groups available are: %s" % ", ".join(SETS))
    print("The 'ALL' set is a superset containing all the other groups")
    print("Use '*' as a wildcard group")
    print("Use the delay to start profiling after a certain amount of time and to avoid")
    print("profiling the initial setup code. This can only apply to the main thread.")
    print("Use the runtime to automatically terminate the process after the given amount of time,")
    print("the time starts counting after the start delay.")
    print("")
    print("Examples:")
    print("#profile server:")
    print("%s -i '*' -e ALL -- start :10" % cmd)
    print("#profile client:")
    print("%s -i '*' -e ALL -- attach  :10 --opengl=no --csc-module=cython" % cmd)
    print("#profile the client's draw thread:")
    print("%s -t draw -i '*' -e ALL -- attach  :10" % cmd)
    print("#profile the client's write thread without logging for 10 seconds, xpra runs without mmap and with x264 as primary encoding:")
    print("%s -t write -i '*' -e logging -r 10 -- attach  :10  --no-mmap --encoding=x264" % cmd)
    print("#profile server encode thread, excluding standard libraries")
    print("%s -t encode -i '*' -e std -e libs -- start :10" % cmd)
    sys.exit(1)


pos = 0
for x in sys.argv:
    if x=="--":
        break
    if x=="-h" or x=="--help":
        usage()
    pos += 1
if pos==0 or pos==len(sys.argv):
    usage("invalid number of arguments")

cg_args = sys.argv[1:pos]
sys.argv = sys.argv[:1]+sys.argv[pos+1:]
if len(cg_args)%2!=0:
    usage("invalid number of arguments")

pairs = []
for i in range(len(cg_args)/2):
    pairs.append((cg_args[i*2], cg_args[i*2+1]))

def get_groups(v):
    r = []
    for x in v.split(","):
        if x=="*":
            return ["*"]
        if x not in SETS:
            usage("invalid package group '%s', options are: %s" % (x, SETS))
            return
        r += globals()[x]
    return r

exclude = []
include = []
trace_thread = None
delay = 0
runtime = 0
for a,v in pairs:
    if a not in ("-i", "-I", "-e", "-E", "-d", "-r", "-t"):
        usage("invalid argument: %s" % a)
    if a=="-i":
        include += get_groups(v)
    elif a=="-I":
        include += v.split(",")
    elif a=="-e":
        exclude += get_groups(v)
    elif a=="-E":
        exclude += v.split(",")
    elif a=="-d":
        delay  = int(v)
    elif a=="-r":
        runtime  = int(v)
    elif a=="-t":
        if trace_thread:
            usage("only one thread can be traced at a time")
        if v not in THREAD_NAMES:
            usage("invalid thread name: %s, options are: %s" % (v, THREAD_NAMES))
        trace_thread=v
    else:
        usage("impossible!")

print("")
print("include=%s" % str(include))
print("exclude=%s" % str(exclude))
print("trace_thread=%s" % trace_thread)
print("delay=%s" % delay)
print("runtime=%s" % runtime)
print("")
if delay>0 and trace_thread:
    usage("delay (-d DELAY) cannot be used with the thread parameter (-t THREAD)")

#adjust cache size:
import fnmatch
fnmatch._MAXCACHE = max(fnmatch._MAXCACHE, len(exclude), len(include)) + 10
fnmatch._purge()

class ExtendedGlobbingFilter(object):
    def __init__(self, include, exclude, default_return):
        self.include = include
        self.exclude = exclude
        self.default_return = default_return

    def __call__(self, full_name=None):
        m = fnmatch.fnmatch
        if any(True for pattern in self.exclude if m(full_name, pattern)):
            return False
        if any(True for pattern in self.include if m(full_name, pattern)):
            return True
        return self.default_return

graphviz = GraphvizOutput()
graphviz.output_file = "./pycallgraph-xpra.png"
config = Config(groups=True)
default_return = not bool(include)
config.trace_filter = ExtendedGlobbingFilter(include, exclude, default_return)
config.include_stdlib = bool(trace_thread)
#config.threaded = bool(trace_thread)
pcg = PyCallGraph(output=graphviz, config=config)

if trace_thread:
    from xpra import daemon_thread
    saved_make_daemon_thread = daemon_thread.make_daemon_thread
    trace_count = 0
    def make_trace_daemon_thread(target, name):
        def trace_target(*args):
            global trace_count
            tracing = name==trace_thread and trace_count==0
            if tracing:
                trace_count += 1
                print("started tracing  %s : %s" % (name.rjust(16), target))
                pcg.start(reset=False)
            else:
                print("not tracing      %s : %s" % (name.rjust(16), target))
            target()
            print("ended            %s : %s" % (name.rjust(16), target))
            if tracing:
                trace_count -= 0
                if trace_count<=0:
                    pcg.stop()
        return saved_make_daemon_thread(trace_target, name)
    daemon_thread.make_daemon_thread = make_trace_daemon_thread
else:
    def do_start_trace(*args):
        print("starting trace")
        pcg.start()
    #trace main thread
    if delay==0:
        do_start_trace()
    else:
        gobject.timeout_add(delay*1000, do_start_trace)

if runtime>0:
    def force_exit(*args):
        print("runtime %s expired, using SIGINT to force exit" % runtime)
        import signal
        os.kill(os.getpid(), signal.SIGINT)
    gobject.timeout_add((runtime+delay)*1000, force_exit)

#ensure we don't call os._exit() when trying to force quit:
def no_force_quit(*args):
    print("no_force_quit%s called" % str(args))
from xpra import os_util
os_util.force_quit = no_force_quit

print("calling xpra with: %s" % str(sys.argv))
import xpra.scripts.main
x = xpra.scripts.main.main(__file__, sys.argv)
print("xpra main returned %s" % x)

if not trace_thread:
    pcg.stop()

graphviz.start()
graphviz.done()

sys.exit(x)
