Commits

jayven committed 5713f22 Merge

Merge refactor

  • Participants
  • Parent commits 175d9b2, 65946d8

Comments (0)

Files changed (5)

+77ea0c742f952cacf4dbcca99225e79a3f6a5c03 r0.0.1
 # -*- coding: utf-8 -*-
 # vim: set ai et si ts=4 sts=4 sw=4:
 
-import sys
+import sys, json
 import pooler
 import linecache
+import base64
+from datetime import datetime
 from itertools import izip, islice
 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)
+
+    def export_tb(self, cr, uid, ids, context=None):
+        rec = self.browse(cr, uid, ids[0], context=context)
+        res = {
+            'last_type': rec.last_type,
+            'last_value': rec.last_value,
+            'last_traceback': [],
+        }
+        for tb in rec.last_traceback:
+            t = {
+                'depth': tb.depth,
+                'filename': tb.filename,
+                'where': tb.where,
+                'lineno': tb.lineno,
+                'line': tb.line,
+                'lines': tb.lines,
+                'locals': [],
+            }
+            for loc in tb.locals:
+                l = {
+                    'name': loc.name,
+                    'value': loc.value,
+                }
+                t['locals'].append((0, 0, l))
+            res['last_traceback'].append((0, 0, t))
+        res = base64.b64encode(json.dumps(res, separators=(',', ':')))
+        res_id = self.pool.get('hotdebug.tb.export').create(cr, uid, {
+            'file': res,
+            'filename': datetime.now().strftime(
+                'TracebackExport-%Y%m%d%H%M%S'),
+        }, context=context)
+        return {
+            'name': 'Export Traceback',
+            'view_mode': 'form',
+            'view_id': False,
+            'view_type': 'form',
+            'res_id': res_id,
+            'res_model': 'hotdebug.tb.export',
+            'type': 'ir.actions.act_window',
+            'nodestroy': True,
+            'target': 'new',
+            'context': context,
+        }
+
+    def import_tb(self, cr, uid, ids, context=None):
+        res_id = self.pool.get('hotdebug.tb.import').create(cr, uid, {
+            'console': ids[0],
+        }, context=context)
+        return {
+            'name': 'Import Traceback',
+            'view_mode': 'form',
+            'view_id': False,
+            'view_type': 'form',
+            'res_id': res_id,
+            'res_model': 'hotdebug.tb.import',
+            'type': 'ir.actions.act_window',
+            'nodestroy': True,
+            'target': 'new',
+            'context': context,
+        }
+
+    def clear_tb(self, cr, uid, ids, context=None):
+        self._clear_exc(cr, uid, ids[0], context)
+        return True
         
     _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,
     }
 
     }
 
 TracebackLocals()
+
+
+class TracebackExport(osv.osv_memory):
+
+    _name = 'hotdebug.tb.export'
+    _columns = {
+        'filename': fields.char('Export File Name', size=128),
+        'file': fields.binary('Export File'),
+    }
+
+
+TracebackExport()
+
+
+class TracebackImport(osv.osv_memory):
+
+    def import_tb(self, cr, uid, ids, context=None):
+        rec = self.browse(cr, uid, ids[0], context=context)
+        if not rec.file:
+            raise osv.except_osv('Error', 'Please select import file')
+        try:
+            value = json.loads(base64.b64decode(rec.file))
+        except ValueError:
+            raise osv.except_osv('Error', 'Bad content')
+        # TODO: more check for value
+        console_obj = self.pool.get('hotdebug.console')
+        console_id = rec.console.id
+        console_obj._clear_exc(cr, uid, console_id, context)
+        console_obj.write(cr, uid, [console_id,], value, context=context)
+        return {'type': 'ir.actions.act_window_close'}
+
+    _name = 'hotdebug.tb.import'
+    _columns = {
+        'console': fields.many2one('hotdebug.console', 'Console',
+            readonly=True),
+        'filename': fields.char('Import File Name', size=128),
+        'file': fields.binary('Import File'),
+    }
+
+
+TracebackImport()

File debugger.xml

 <?xml version="1.0" encoding="utf-8"?>
 <openerp>
     <data>
+        <record id="view_hotdebug_tb_export" model="ir.ui.view">
+            <field name="name">hotdebug.tb.export</field>
+            <field name="model">hotdebug.tb.export</field>
+            <field name="type">form</field>
+            <field name="arch" type="xml">
+                <form string="Export Tracebacks">
+                    <field name="filename" select="1" colspan="4"/>
+                    <field name="file" filename="filename" colspan="4"/>
+                </form>
+            </field>
+        </record>
+
+        <record id="view_hotdebug_tb_import" model="ir.ui.view">
+            <field name="name">hotdebug.tb.import</field>
+            <field name="model">hotdebug.tb.import</field>
+            <field name="type">form</field>
+            <field name="arch" type="xml">
+                <form string="Import Tracebacks">
+                    <field name="filename" select="1" colspan="4"/>
+                    <field name="file" filename="filename" colspan="4"/>
+                    <button colspan="4" name="import_tb" string="Import it"
+                        type="object" icon="gtk-go-forward" />
+                </form>
+            </field>
+        </record>
+
         <record id="view_hotdebug_tb_locals_tree" model="ir.ui.view">
             <field name="name">hotdebug.tb.locals.tree</field>
             <field name="model">hotdebug.tb.locals</field>
                         <page string="Last exception">
                             <field name="catch_console_exception" invisible="1" />
                             <field name="catch_server_exception" invisible="1" />
-                            <button colspan="2" name="set_catch_console_exception" string="Catch console exception"
+                            <button colspan="2" name="set_catch_console_exception" string="Start to catch console exception"
                                 attrs="{'readonly':[('catch_console_exception','=',True)]}"
                                 context="{'catch_console_exception':True}"
                                 type="object" icon="gtk-go-forward" />
-                            <button colspan="2" name="set_catch_console_exception" string="Don't catch console exception"
+                            <button colspan="2" name="set_catch_console_exception" string="Stop to catch console exception"
                                 attrs="{'readonly':[('catch_console_exception','=',False)]}"
                                 context="{'catch_console_exception':False}"
                                 type="object" icon="gtk-go-forward" />
-                            <button colspan="2" name="set_catch_server_exception" string="Catch server exception (remember to press fresh)"
+                            <button colspan="2" name="set_catch_server_exception" string="Start to catch server exception (remember to press fresh)"
                                 attrs="{'readonly':[('catch_server_exception','=',True)]}"
                                 context="{'catch_server_exception':True}"
                                 type="object" icon="gtk-go-forward" />
-                            <button colspan="2" name="set_catch_server_exception" string="Don't catch server exception"
+                            <button colspan="2" name="set_catch_server_exception" string="Stop to catch server exception"
                                 attrs="{'readonly':[('catch_server_exception','=',False)]}"
                                 context="{'catch_server_exception':False}"
                                 type="object" icon="gtk-go-forward" />
                             <separator colspan="4" string="Last exception value" />
                             <field name="last_value" nolabel="1" colspan="4" />
                             <field name="last_traceback"  string="tracebacks" widget="one2many_list" nolabel="1" readonly="1" colspan="4" />
+                            <newline />
+                            <button colspan="2" name="clear_tb" string="Clear Tracebacks"
+                                type="object" icon="gtk-go-forward" />
+                            <newline />
+                            <button colspan="2" name="export_tb" string="Export Tracebacks"
+                                type="object" icon="gtk-go-forward" />
+                            <button colspan="2" name="import_tb" string="Import Tracebacks"
+                                type="object" icon="gtk-go-forward" />
+                        </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"/>
+#!/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

File 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
-
-