openerp-community team mailing list archive
-
openerp-community team
-
Mailing list archive
-
Message #00110
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