You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

146 lines
5.7 KiB
Python

8 months ago
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import base64
import datetime
import json
import logging
from dateutil.relativedelta import relativedelta
from odoo import fields, models, api, _
from odoo.exceptions import UserError
from odoo.http import request
from odoo.tools.profiler import make_session
from odoo.tools.speedscope import Speedscope
_logger = logging.getLogger(__name__)
class IrProfile(models.Model):
_name = 'ir.profile'
_description = 'Profiling results'
_log_access = False # avoid useless foreign key on res_user
_order = 'session desc, id desc'
create_date = fields.Datetime('Creation Date')
session = fields.Char('Session', index=True)
name = fields.Char('Description')
duration = fields.Float('Duration')
init_stack_trace = fields.Text('Initial stack trace', prefetch=False)
sql = fields.Text('Sql', prefetch=False)
sql_count = fields.Integer('Queries Count')
traces_async = fields.Text('Traces Async', prefetch=False)
traces_sync = fields.Text('Traces Sync', prefetch=False)
qweb = fields.Text('Qweb', prefetch=False)
entry_count = fields.Integer('Entry count')
speedscope = fields.Binary('Speedscope', compute='_compute_speedscope')
speedscope_url = fields.Text('Open', compute='_compute_speedscope_url')
@api.autovacuum
def _gc_profile(self):
# remove profiles older than 30 days
domain = [('create_date', '<', fields.Datetime.now() - datetime.timedelta(days=30))]
return self.sudo().search(domain).unlink()
def _compute_speedscope(self):
for execution in self:
sp = Speedscope(init_stack_trace=json.loads(execution.init_stack_trace))
if execution.sql:
sp.add('sql', json.loads(execution.sql))
if execution.traces_async:
sp.add('frames', json.loads(execution.traces_async))
if execution.traces_sync:
sp.add('settrace', json.loads(execution.traces_sync))
result = json.dumps(sp.add_default().make())
execution.speedscope = base64.b64encode(result.encode('utf-8'))
def _compute_speedscope_url(self):
for profile in self:
profile.speedscope_url = f'/web/speedscope/{profile.id}'
def _enabled_until(self):
"""
If the profiling is enabled, return until when it is enabled.
Otherwise return ``None``.
"""
limit = self.env['ir.config_parameter'].sudo().get_param('base.profiling_enabled_until', '')
return limit if str(fields.Datetime.now()) < limit else None
@api.model
def set_profiling(self, profile=None, collectors=None, params=None):
"""
Enable or disable profiling for the current user.
:param profile: ``True`` to enable profiling, ``False`` to disable it.
:param list collectors: optional list of collectors to use (string)
:param dict params: optional parameters set on the profiler object
"""
# Note: parameters are coming from a rpc calls or route param (public user),
# meaning that corresponding session variables are client-defined.
# This allows to activate any profiler, but can be
# dangerous handling request.session.profile_collectors/profile_params.
if profile:
limit = self._enabled_until()
_logger.info("User %s started profiling", self.env.user.name)
if not limit:
request.session.profile_session = None
if self.env.user._is_system():
return {
'type': 'ir.actions.act_window',
'view_mode': 'form',
'res_model': 'base.enable.profiling.wizard',
'target': 'new',
'views': [[False, 'form']],
}
raise UserError(_('Profiling is not enabled on this database. Please contact an administrator.'))
if not request.session.profile_session:
request.session.profile_session = make_session(self.env.user.name)
request.session.profile_expiration = limit
if request.session.profile_collectors is None:
request.session.profile_collectors = []
if request.session.profile_params is None:
request.session.profile_params = {}
elif profile is not None:
request.session.profile_session = None
if collectors is not None:
request.session.profile_collectors = collectors
if params is not None:
request.session.profile_params = params
return {
'session': request.session.profile_session,
'collectors': request.session.profile_collectors,
'params': request.session.profile_params,
}
class EnableProfilingWizard(models.TransientModel):
_name = 'base.enable.profiling.wizard'
_description = "Enable profiling for some time"
duration = fields.Selection([
('minutes_5', "5 Minutes"),
('hours_1', "1 Hour"),
('days_1', "1 Day"),
('months_1', "1 Month"),
], string="Enable profiling for")
expiration = fields.Datetime("Enable profiling until", compute='_compute_expiration', store=True, readonly=False)
@api.depends('duration')
def _compute_expiration(self):
for record in self:
unit, quantity = (record.duration or 'days_0').split('_')
record.expiration = fields.Datetime.now() + relativedelta(**{unit: int(quantity)})
def submit(self):
self.env['ir.config_parameter'].set_param('base.profiling_enabled_until', self.expiration)
return False