jayven avatar jayven committed 635c8f2

refactor and add profile feature to hotdebug

Comments (0)

Files changed (4)

 from pprint import pformat
 from osv import osv, fields
 
+import hook
 from bgconsole import BGConsole
-from hook_excpetion import register_exception_callback, \
-    unregister_exception_callback
+try:
+    import pstats
+except ImportError:
+    pstats = None
 
 
 help_text = """
         cr: the cursor
         pool: the object pool
 
+For complete usage, goto: https://bitbucket.org/jayven/hotdebug
+
 """
 
 def norm_select(select):
                             last_traceback, context)
                 finally:
                     cr.close()
-            register_exception_callback(id, cb)
+            hook.register_exception_callback(id, cb)
         else:
-            unregister_exception_callback(id)
+            hook.unregister_exception_callback(id)
         return self.write(cr, uid, ids, {
             'catch_server_exception': catch_server_exception,
         }, context=context)
+
+    def set_profile(self, cr, uid, ids, context=None):
+        if pstats is None:
+            raise osv.except_osv('Error', 'Package \'pstats\' is not '\
+                    'installed, please install it first')
+
+        if context is None:
+            context = {}
+        profile_server = context.get('profile_server', False)
+        id = ids[0]
+        rec = self.browse(cr, uid, id, context=context)
+        dbname = cr.dbname
+        if profile_server:
+            started = hook.start_profile(id)
+            if not started:
+                raise osv.except_osv('Error', 'Another application may be '\
+                        'already profiling, please wait until it is stopped')
+            profile_result = False
+        else:
+            profile_result = hook.stop_profile(id, rec.profile_sort)
+            if profile_result is None:
+                raise osv.except_osv('Error', 'Missing profile')
+        return self.write(cr, uid, ids, {
+            'profile_server': profile_server,
+            'profile_result': profile_result,
+        }, context=context)
         
     _name = 'hotdebug.console'
     _columns = {
             readonly=True),
         'catch_server_exception': fields.boolean('Catch server exception?',
             readonly=True),
+        'profile_server': fields.boolean('Profile server activity', readonly=True),
+        'profile_sort': fields.selection([
+            ('time', 'internal time'),
+            ('cumulative', 'cumulative time'),
+            ('calls', 'call count'),
+        ], 'Profile result order by'),
+        'profile_result': fields.text('Profile result', readonly=True),
         'help': fields.text('Help', readonly=True),
     }
     _defaults = {
         'last_value': '',
         'catch_console_exception': True,
         'catch_server_exception': False,
+        'profile_server': False,
+        'profile_sort': 'time',
+        'profile_result': (pstats is None) and """
+            Profile feature disabled since: 
+                Package 'pstats' is not installed. Please install it first 
+                (e.g. apt-get install python-profiler)
+        """ or "",
         'help': help_text,
     }
 
                             <field name="last_value" nolabel="1" colspan="4" />
                             <field name="last_traceback"  string="tracebacks" widget="one2many_list" nolabel="1" readonly="1" colspan="4" />
                         </page>
+                        <page string="Profile">
+                            <field name="profile_server" invisible="1" />
+                            <button colspan="2" name="set_profile" string="Start profiling"
+                                attrs="{'readonly':[('profile_server','=',True)]}"
+                                context="{'profile_server':True}"
+                                type="object" icon="gtk-go-forward" />
+                            <button colspan="2" name="set_profile" string="Stop profiling"
+                                attrs="{'readonly':[('profile_server','=',False)]}"
+                                context="{'profile_server':False}"
+                                type="object" icon="gtk-go-forward" />
+                            <field name="profile_sort" />
+                            <newline />
+                            <field name="profile_result" nolabel="1" colspan="4" />
+                        </page>
                         <page string="Help">
                             <field name="help" nolabel="1" colspan="4"/>
                         </page>
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# vim: set ai et si ts=4 sts=4 sw=4:
+
+from __future__ import with_statement
+import sys, threading
+from cProfile import Profile
+from cStringIO import StringIO
+try:
+    import pstats
+except ImportError:
+    pstats = None
+
+
+class RLock(object):
+
+    def __init__(self):
+        self._lock = threading.RLock()
+
+    def __enter__(self):
+        self._lock.acquire()
+        return self
+    
+    def __exit__(self, exc_type, exc_val, exc_tb):
+        self._lock.release()
+
+
+def _hook_export_service():
+
+    def public(fn):
+        globals()[fn.__name__] = fn
+        return fn
+
+    _wrappers = []
+    def wrapper(fn):
+        _wrappers.append(fn)
+        return fn
+
+    # ===  catch exceptions ===
+
+    # exception callback lock held when processing callbacks
+    _exc_lock = RLock()
+
+    _exc_cbs = {}
+
+    @public
+    def register_exception_callback(id, cb):
+        '''
+        Register callback that will be called when has uncaught exception
+        '''
+        with _exc_lock:
+            _exc_cbs[id] = cb
+
+    @public
+    def unregister_exception_callback(id):
+        '''
+        Reverse op of register_exception_callback
+        '''
+        with _exc_lock:
+            _exc_cbs.pop(id, None)
+
+    @wrapper
+    def wrap_catch_exc(fn):
+
+        def wrapped(*args, **kw):
+            try:
+                return fn(*args, **kw)
+            except:
+                # there may be several threads that raise exception
+                # so one-by-one
+                last_type, last_value, last_traceback = sys.exc_info()
+                with _exc_lock:
+                    for cb in _exc_cbs.itervalues():
+                        cb(last_type, last_value, last_traceback)
+                raise
+
+        return wrapped
+
+    # === profile ===
+
+    _profile_objs = {}
+
+    _profile_lock = RLock()
+
+    @public
+    def start_profile(id):
+        '''
+        Start to profile
+        '''
+        if pstats is None:
+            return False
+
+        if _profile_objs:
+            return False
+        with _profile_lock:
+            _profile_objs[id] = Profile()
+        return True
+    
+    @public
+    def stop_profile(id, sort="time"):
+        '''
+        Stop to profile and return result
+        '''
+        if pstats is None:
+            return None
+
+        with _profile_lock:
+            p = _profile_objs.pop(id, None)
+            if p is None:
+                return None
+            result = StringIO()
+            pstats.Stats(p, stream=result).strip_dirs().sort_stats(
+                    sort).print_stats()
+            return result.getvalue()
+
+    @wrapper
+    def wrap_profile(fn):
+
+        def wrapped(*args, **kw):
+            if _profile_objs:
+                with _profile_lock:
+                    _profile_obj = _profile_objs.values()[0]
+                    return _profile_obj.runcall(fn, *args, **kw)
+            return fn(*args, **kw)
+        
+        return wrapped
+
+    # === modify ===
+
+    from netsvc import ExportService
+    for service in ExportService._services.itervalues():
+        dispatch = service.__class__.__dict__.get('dispatch', None)
+        if dispatch and not getattr(dispatch, '_wrapped', False):
+            for w in _wrappers:
+                dispatch = w(dispatch)
+            dispatch._wrapped = True
+            service.__class__.dispatch = dispatch
+
+
+_hook_export_service()
+del _hook_export_service

hook_excpetion.py

-#!/usr/bin/env python
-# -*- coding: utf-8 -*-
-# vim: set ai et si ts=4 sts=4 sw=4:
-
-import sys, threading
-from release import major_version
-
-
-__all__ = ['register_exception_callback', 'unregister_exception_callback']
-
-
-# exception callback
-_exception_callback = {}
-
-
-# exception callback lock held when processing callbacks
-_exception_callback_lock = threading.RLock()
-
-
-def register_exception_callback(id, cb):
-    '''
-    Register callback that will be called when has uncaught exception
-    '''
-    _exception_callback_lock.acquire()
-    try:
-        _exception_callback[id] = cb
-    finally:
-        _exception_callback_lock.release()
-
-
-def unregister_exception_callback(id):
-    '''
-    Reverse op of register_exception_callback
-    '''
-    _exception_callback_lock.acquire()
-    try:
-        _exception_callback.pop(id, None)
-    finally:
-        _exception_callback_lock.release()
-
-
-def _hook_excpetion():
-
-    if major_version == '6.0':
-        def _wrap_dispatch(old):
-            def dispatch(self, method, auth, params):
-                try:
-                    return old(self, method, auth, params)
-                except:
-                    # there may be several threads that raise exception
-                    # so one-by-one
-                    last_type, last_value, last_traceback = sys.exc_info()
-                    _exception_callback_lock.acquire()
-                    try:
-                        for cb in _exception_callback.itervalues():
-                            cb(last_type, last_value, last_traceback)
-                    finally:
-                        _exception_callback_lock.release()
-                    raise
-            dispatch._hooked = True
-            return dispatch
-    elif major_version == '6.1':
-        def _wrap_dispatch(old):
-            def dispatch(self, method, params):
-                try:
-                    return old(self, method, params)
-                except:
-                    # there may be several threads that raise exception
-                    # so one-by-one
-                    last_type, last_value, last_traceback = sys.exc_info()
-                    _exception_callback_lock.acquire()
-                    try:
-                        for cb in _exception_callback.itervalues():
-                            cb(last_type, last_value, last_traceback)
-                    finally:
-                        _exception_callback_lock.release()
-                    raise
-            dispatch._hooked = True
-            return dispatch
-
-    from netsvc import ExportService
-    for service in ExportService._services.itervalues():
-        dispatch = service.__class__.__dict__.get('dispatch', None)
-        if dispatch and not getattr(dispatch, '_hooked', False):
-            service.__class__.dispatch = _wrap_dispatch(dispatch)
-
-
-_hook_excpetion()
-del _hook_excpetion
-
-
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.