← Back to team overview

openerp-community team mailing list archive

lp:~openerp-community/openobject-server/edannenberg_frequentcronjobs into lp:openobject-server

 

Erik Dannenberg (bloopark systems) has proposed merging lp:~openerp-community/openobject-server/edannenberg_frequentcronjobs into lp:openobject-server.

Requested reviews:
  OpenERP Core Team (openerp)

-- 
https://code.launchpad.net/~openerp-community/openobject-server/edannenberg_frequentcronjobs/+merge/39290
Your team OpenERP Community is subscribed to branch lp:~openerp-community/openobject-server/edannenberg_frequentcronjobs.
=== modified file 'bin/addons/base/ir/ir.xml'
--- bin/addons/base/ir/ir.xml	2010-10-17 18:16:21 +0000
+++ bin/addons/base/ir/ir.xml	2010-10-25 16:18:50 +0000
@@ -1428,31 +1428,52 @@
             </field>
         </record>
 
-        <record id="ir_cron_view" model="ir.ui.view">
+	<record id="ir_cron_view" model="ir.ui.view">
             <field name="name">ir.cron.form</field>
             <field name="model">ir.cron</field>
             <field name="type">form</field>
             <field name="arch" type="xml">
                 <form string="Scheduled Actions">
-                    <field name="name"/>
-                    <field name="active"/>
-                    <field name="user_id" />
-                    <field name="priority" />
                     <notebook colspan="4">
                     <page string="Information">
+                        <field name="name" select="1"/>
+                        <field name="active" select="1"/>
+                        <field name="user_id" select="1"/>
+                        <field name="priority" select="1"/>
+                        <newline/>
                         <field name="interval_number"/>
                         <field name="interval_type"/>
                         <newline/>
                         <field name="nextcall"/>
                         <field name="numbercall"/>
                         <field name="doall"/>
+                        <field name="add_runtime"/>
+                        <separator string="Statistics" colspan="4"/>
+                        <field name="track_runtime"/>
+                        <newline/>
+                        <group colspan="2" col="2" attrs="{'invisible':[('track_runtime', '!=', 1)]}">
+                        	<field name="runtime_avg_runs_max" readonly="False"/>
+						</group>
                     </page>
                     <page string="Technical Data" groups="base.group_extended">
                         <separator string="Action to Trigger" colspan="4"/>
                         <field name="model" groups="base.group_extended"/>
                         <field name="function"/>
-                        <separator string="Arguments" colspan="4"/>
-                        <field colspan="4" name="args" nolabel="1"/>
+                        <field colspan="4" name="args"/>
+                    </page>
+                    <page string="Statistical Data" attrs="{'invisible':[('track_runtime', '!=', 1)]}">
+                        <field name="runtime_last" readonly="True"/>
+                        <field name="runtime_last_on" readonly="True"/>
+                        <newline/>
+                        <field name="runtime_min" readonly="True"/>
+                        <field name="runtime_min_on" readonly="True"/>
+                        <newline/>
+                        <field name="runtime_max" readonly="True"/>
+                        <field name="runtime_max_on" readonly="True"/>
+                        <newline/>
+                        <field name="runtime_avg" readonly="True"/>
+                        <field name="runtime_avg_on" readonly="True"/>
+                        <field name="runtime_avg_runs_cur" readonly="True"/>
                     </page>
                     </notebook>
                 </form>

=== modified file 'bin/addons/base/ir/ir_cron.py'
--- bin/addons/base/ir/ir_cron.py	2010-10-04 14:39:04 +0000
+++ bin/addons/base/ir/ir_cron.py	2010-10-25 16:18:50 +0000
@@ -1,4 +1,4 @@
-# -*- coding: utf-8 -*-
+# -*- encoding: utf-8 -*-
 ##############################################################################
 #
 #    OpenERP, Open Source Management Solution
@@ -20,25 +20,24 @@
 #
 ##############################################################################
 
+from mx import DateTime
 import time
-from datetime import datetime
-from dateutil.relativedelta import relativedelta
 import netsvc
 import tools
+import pooler
+from osv import fields,osv
 from tools.safe_eval import safe_eval as eval
-import pooler
-from osv import fields, osv
 
 def str2tuple(s):
     return eval('tuple(%s)' % (s or ''))
 
 _intervalTypes = {
-    'work_days': lambda interval: relativedelta(days=interval),
-    'days': lambda interval: relativedelta(days=interval),
-    'hours': lambda interval: relativedelta(hours=interval),
-    'weeks': lambda interval: relativedelta(days=7*interval),
-    'months': lambda interval: relativedelta(months=interval),
-    'minutes': lambda interval: relativedelta(minutes=interval),
+    'work_days': lambda interval: DateTime.RelativeDateTime(days=interval),
+    'days': lambda interval: DateTime.RelativeDateTime(days=interval),
+    'hours': lambda interval: DateTime.RelativeDateTime(hours=interval),
+    'weeks': lambda interval: DateTime.RelativeDateTime(days=7*interval),
+    'months': lambda interval: DateTime.RelativeDateTime(months=interval),
+    'minutes': lambda interval: DateTime.RelativeDateTime(minutes=interval),
 }
 
 class ir_cron(osv.osv, netsvc.Agent):
@@ -47,16 +46,32 @@
         'name': fields.char('Name', size=60, required=True),
         'user_id': fields.many2one('res.users', 'User', required=True),
         'active': fields.boolean('Active'),
-        'interval_number': fields.integer('Interval Number',help="Repeat every x."),
+        'interval_number': fields.integer('Interval Number'),
         'interval_type': fields.selection( [('minutes', 'Minutes'),
             ('hours', 'Hours'), ('work_days','Work Days'), ('days', 'Days'),('weeks', 'Weeks'), ('months', 'Months')], 'Interval Unit'),
-        'numbercall': fields.integer('Number of Calls', help='Number of time the function is called,\na negative number indicates no limit'),
-        'doall' : fields.boolean('Repeat Missed', help="Enable this if you want to execute missed occurences as soon as the server restarts."),
-        'nextcall' : fields.datetime('Next Execution Date', required=True, help="Next planned execution date for this scheduler"),
-        'model': fields.char('Object', size=64, help="Name of object whose function will be called when this scheduler will run. e.g. 'res.partener'"),
-        'function': fields.char('Function', size=64, help="Name of the method to be called on the object when this scheduler is executed."),
-        'args': fields.text('Arguments', help="Arguments to be passed to the method. e.g. (uid,)"),
-        'priority': fields.integer('Priority', help='0=Very Urgent\n10=Not urgent')
+        'numbercall': fields.integer('Number of Calls', help='Number of time the function is called,\na negative number indicates that the 
+function will always be called'),
+        'doall' : fields.boolean('Repeat Missed'),
+        'add_runtime' : fields.boolean('Add Runtime to Interval', help='Add runtime of the last job to next job interval, usefull for jobs 
+with minute intervals where the job runtime may be longer then the job interval.'),
+        'nextcall' : fields.datetime('Next Call Date', required=True),
+        'model': fields.char('Object', size=64),
+        'function': fields.char('Function', size=64),
+        'args': fields.text('Arguments'),
+        'priority': fields.integer('Priority', help='0=Very Urgent\n10=Not urgent'),
+        'track_runtime': fields.boolean('Track Runtime', help='Keep track of shortest/longest/last runtime of this job'),
+        'runtime_min': fields.float('Fastest job run', digits=(12,6)),
+        'runtime_min_on': fields.datetime('Date of fastest job run'),
+        'runtime_max': fields.float('Longest job run', digits=(12,6)),
+        'runtime_max_on': fields.datetime('Date of longest job run'),
+        'runtime_avg': fields.float('Average job runtime', digits=(12,6)),
+        'runtime_avg_runs_cur': fields.integer('Job runs in current interval'),
+        'runtime_avg_runs_max': fields.integer('Iterations for average runtime', help='The amount of iterations to use for calculating the 
+average runtime of this job. Use 0 for infinite runs. Example: Job runs once every hour, to reset average runtime calculation every week: 
+24*7=168 iterations.'),
+        'runtime_avg_on': fields.datetime('Date last average interval started'),
+        'runtime_last': fields.float('Duration of last job run', digits=(12,6)),
+        'runtime_last_on': fields.datetime('Date of last job run'),
     }
 
     _defaults = {
@@ -67,7 +82,15 @@
         'interval_type' : lambda *a: 'months',
         'numbercall' : lambda *a: 1,
         'active' : lambda *a: 1,
-        'doall' : lambda *a: 1
+        'doall' : lambda *a: 1,
+        'add_runtime' : lambda *a: 0,
+        'track_runtime' : lambda *a: 0,
+        'runtime_min' : lambda *a: 0,
+        'runtime_max' : lambda *a: 0,
+        'runtime_avg' : lambda *a: 0,
+        'runtime_avg_runs_cur' : lambda *a: 0,
+        'runtime_avg_runs_max' : lambda *a: 100,
+        'runtime_last' : lambda *a: 0,
     }
 
     def _check_args(self, cr, uid, ids, context=None):
@@ -78,7 +101,7 @@
             return False
         return True
 
-    _constraints = [
+    _constraints= [
         (_check_args, 'Invalid arguments', ['args']),
     ]
 
@@ -90,7 +113,8 @@
             try:
                 f(cr, uid, *args)
             except Exception, e:
-                self._logger.notifyChannel('timers', netsvc.LOG_ERROR, "Job call of self.pool.get('%s').%s(cr, uid, *%r) failed" % (model, func, args))
+                self._logger.notifyChannel('timers', netsvc.LOG_ERROR, "Job call of self.pool.get('%s').%s(cr, uid, *%r) failed" % (model, 
+func, args))
                 self._logger.notifyChannel('timers', netsvc.LOG_ERROR, tools.exception_to_unicode(e))
 
 
@@ -102,10 +126,10 @@
         cr = db.cursor()
         try:
             if not pool._init:
-                now = datetime.now()
+                now = DateTime.now()
                 cr.execute('select * from ir_cron where numbercall<>0 and active and nextcall<=now() order by priority')
                 for job in cr.dictfetchall():
-                    nextcall = datetime.strptime(job['nextcall'], '%Y-%m-%d %H:%M:%S')
+                    nextcall = DateTime.strptime(job['nextcall'], '%Y-%m-%d %H:%M:%S')
                     numbercall = job['numbercall']
 
                     ok = False
@@ -113,36 +137,71 @@
                         if numbercall > 0:
                             numbercall -= 1
                         if not ok or job['doall']:
+                            if job['track_runtime']:
+                                callback_start = DateTime.now()
                             self._callback(cr, job['user_id'], job['model'], job['function'], job['args'])
+                            if job['track_runtime']:
+                                callback_runtime = DateTime.now() - callback_start
                         if numbercall:
-                            nextcall += _intervalTypes[job['interval_type']](job['interval_number'])
+                            if job['add_runtime']:
+                                nextcall = DateTime.now() + _intervalTypes[job['interval_type']](job['interval_number'])
+                            else:
+                                nextcall += _intervalTypes[job['interval_type']](job['interval_number'])
                         ok = True
-                    addsql = ''
+                    addsql=''
                     if not numbercall:
                         addsql = ', active=False'
-                    cr.execute("update ir_cron set nextcall=%s, numbercall=%s"+addsql+" where id=%s", (nextcall.strftime('%Y-%m-%d %H:%M:%S'), numbercall, job['id']))
+                    if job['track_runtime']:
+                        addsql += self.get_tracking_sql(job, callback_start, callback_runtime)
+                            
+                    cr.execute("update ir_cron set nextcall=%s, numbercall=%s"+addsql+" where id=%s", (nextcall.strftime('%Y-%m-%d 
+%H:%M:%S'), numbercall, job['id']))
                     cr.commit()
 
-
-            cr.execute('select min(nextcall) as min_next_call from ir_cron where numbercall<>0 and active and nextcall>=now()')
-            next_call = cr.dictfetchone()['min_next_call']
-            if next_call:
-                next_call = time.mktime(time.strptime(next_call, '%Y-%m-%d %H:%M:%S'))
-            else:
-                next_call = int(time.time()) + 3600   # if do not find active cron job from database, it will run again after 1 day
-
-            if not check:
-                self.setAlarm(self._poolJobs, next_call, db_name, db_name)
-
-        except Exception, ex:
-            logger = netsvc.Logger()
-            logger.notifyChannel('cron', netsvc.LOG_WARNING,
-                'Exception in cron:'+str(ex))
+            self.set_next_timer(cr, check)
 
         finally:
             cr.commit()
             cr.close()
 
+    def get_tracking_sql(self, job, callback_start, callback_runtime):
+        sql_string = ''
+        callack_start_sql = callback_start.strftime('%Y-%m-%d %H:%M:%S')
+        callback_runtime_sql = str(callback_runtime.seconds)
+        sql_string += ", runtime_last="+callback_runtime_sql
+        sql_string += ", runtime_last_on='"+callack_start_sql+"'"
+        if callback_runtime <= job['runtime_min'] or job['runtime_min'] == 0.000000:
+            sql_string += ", runtime_min="+callback_runtime_sql
+            sql_string += ", runtime_min_on='"+callack_start_sql+"'"
+        if callback_runtime >= job['runtime_max']:
+            sql_string += ", runtime_max="+callback_runtime_sql
+            sql_string += ", runtime_max_on='"+callack_start_sql+"'"
+        avg_runs_current = job['runtime_avg_runs_cur']
+        avg_runs_runtime = (float(job['runtime_avg'])*float(avg_runs_current)+callback_runtime)/float((avg_runs_current+1))
+        if avg_runs_current == 0:
+            sql_string += ", runtime_avg_on='"+callack_start_sql+"'"   
+        avg_runs_current += 1
+        if avg_runs_current > job['runtime_avg_runs_max'] and job['runtime_avg_runs_max'] != 0:
+            avg_runs_current = 0
+        sql_string += ", runtime_avg_runs_cur="+str(avg_runs_current)
+        sql_string += ", runtime_avg="+str(avg_runs_runtime.seconds)    
+        return sql_string
+            
+    def set_next_timer(self, cr, check=False):
+        cr.execute('select min(nextcall) as min_next_call from ir_cron where numbercall<>0 and active and nextcall>=now()')
+        next_call = cr.dictfetchone()['min_next_call']
+        if next_call:
+            next_call = time.mktime(time.strptime(next_call, '%Y-%m-%d %H:%M:%S'))
+        else:
+            next_call = int(time.time()) + 3600
+
+        if not check:
+            self.setAlarm(self._poolJobs, next_call, cr.dbname, cr.dbname)
+
+    def restart_timer(self, cr):
+        self.cancel(cr.dbname)
+        self.set_next_timer(cr)
+    
     def restart(self, dbname):
         self.cancel(dbname)
         self._poolJobs(dbname)
@@ -150,21 +209,20 @@
     def create(self, cr, uid, vals, context=None):
         res = super(ir_cron, self).create(cr, uid, vals, context=context)
         cr.commit()
-        self.restart(cr.dbname)
+        self.restart_timer(cr)
         return res
 
     def write(self, cr, user, ids, vals, context=None):
         res = super(ir_cron, self).write(cr, user, ids, vals, context=context)
         cr.commit()
-        self.restart(cr.dbname)
+        self.restart_timer(cr)
         return res
 
     def unlink(self, cr, uid, ids, context=None):
         res = super(ir_cron, self).unlink(cr, uid, ids, context=context)
         cr.commit()
-        self.restart(cr.dbname)
+        self.restart_timer(cr)
         return res
 ir_cron()
 
 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
-


Follow ups