Skip to content

Commit 4c4a740

Browse files
committed
[IMP] web, base: add option to profile dispatch
The profiling tools can be useful to profile a test of some execution point but this is not convenient to identify a problem on a running instance. With this commit, an option available in the debug menu allows to add a flag on the user sessions to enable profiling of all requests. Each request will be saved in a different 'ir.profile' entry, but will be grouped under the same session. The profiling can be activated on all sessions, even for a public user, but only if profiling is enabled on the database globally. This commits also adds a speedscope view to visualize saved results in the web client. closes odoo#66590 Signed-off-by: Xavier Dollé (xdo) <xdo@odoo.com>
1 parent 4444475 commit 4c4a740

File tree

19 files changed

+427
-10
lines changed

19 files changed

+427
-10
lines changed

addons/base_setup/models/res_config_settings.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ class ResConfigSettings(models.TransientModel):
4949
language_count = fields.Integer('Number of Languages', compute="_compute_language_count")
5050
company_name = fields.Char(related="company_id.display_name", string="Company Name")
5151
company_informations = fields.Text(compute="_compute_company_informations")
52+
profiling_enabled_until = fields.Datetime("Profiling enabled until", config_parameter='base.profiling_enabled_until')
5253

5354
def open_company(self):
5455
return {

addons/base_setup/views/res_config_settings_views.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,18 @@
366366
</div>
367367
</div>
368368
</div>
369+
370+
<h2 groups="base.group_no_one">Performance</h2>
371+
<div groups="base.group_no_one" class="row mt16 o_settings_container" name="performance">
372+
<div class="col-12 col-lg-6 o_setting_box" id="profiling_enabled_until">
373+
<label for="profiling_enabled_until"/>
374+
<field name="profiling_enabled_until"/>
375+
<div class="text-muted">
376+
Enable the profiling tool. Profiling may impact performance while being active.
377+
</div>
378+
</div>
379+
</div>
380+
369381
<widget name='res_config_dev_tool'/>
370382
<div id='about'>
371383
<h2>About</h2>

addons/portal/views/portal_templates.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@
151151
<template id="user_sign_in" name="User Sign In" inherit_id="portal.placeholder_user_sign_in">
152152
<xpath expr="." position="inside">
153153
<li groups="base.group_public" t-attf-class="#{_item_class} o_no_autohide_item">
154-
<a t-attf-href="/web/login" t-attf-class="#{_link_class}">Sign in</a>
154+
<a t-attf-href="/web/login" t-attf-class="#{_link_class}">Sign in<span t-if="request.session.profile_session" class="text-danger fa fa-circle"/></a>
155155
</li>
156156
</xpath>
157157
</template>

addons/web/__manifest__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
'views/webclient_templates.xml',
1919
'views/report_templates.xml',
2020
'views/base_document_layout_views.xml',
21+
'views/speedscope_template.xml',
2122
'data/report_layout.xml',
2223
],
2324
'assets': {
@@ -61,6 +62,7 @@
6162
'web.assets_common': [
6263
('include', 'web._assets_helpers'),
6364

65+
'web/static/src/scss/debug_profiling.scss',
6466
'web/static/lib/bootstrap/scss/_variables.scss',
6567

6668
('include', 'web._assets_common_styles'),

addons/web/controllers/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
# -*- coding: utf-8 -*-
22
# Part of Odoo. See LICENSE file for full copyright and licensing details.
33

4-
from . import main, pivot
4+
from . import main
5+
from . import pivot
6+
from . import profiling
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# -*- coding: utf-8 -*-
2+
# Part of Odoo. See LICENSE file for full copyright and licensing details.
3+
import json
4+
5+
from odoo.exceptions import UserError
6+
from odoo.http import Controller, request, Response, route
7+
8+
class Profiling(Controller):
9+
10+
@route('/web/set_profiling', type='http', auth='public', sitemap=False)
11+
def profile(self, profile=None, collectors=None, **params):
12+
if collectors is not None:
13+
collectors = collectors.split(',')
14+
else:
15+
collectors = ['sql', 'traces_async']
16+
profile = profile and profile != '0'
17+
try:
18+
state = request.env['ir.profile'].set_profiling(profile, collectors=collectors, params=params)
19+
return json.dumps(state)
20+
except UserError as e:
21+
return Response(response='error: %s' % e, status=500)
22+
23+
@route(['/web/speedscope/', '/web/speedscope/<model("ir.profile"):profile>'], type='http', sitemap=False, auth='user')
24+
def speedscope(self, profile=None):
25+
# don't server speedscope index if profiling is not enabled
26+
if not request.env['ir.profile']._enabled_until():
27+
return request.not_found()
28+
icp = request.env['ir.config_parameter']
29+
context = {
30+
'profile': profile,
31+
'url_root': request.httprequest.url_root,
32+
'cdn': icp.get_param('speedscope_cdn', "https://cdn.jsdelivr.net/npm/speedscope@1.13.0/dist/release/")
33+
}
34+
return request.render('web.view_speedscope_index', context)

addons/web/models/ir_http.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ def session_info(self):
4242
"partner_id": user.partner_id.id if request.session.uid and user.partner_id else None,
4343
"web.base.url": IrConfigSudo.get_param('web.base.url', default=''),
4444
"active_ids_limit": int(IrConfigSudo.get_param('web.active_ids_limit', default='20000')),
45+
'profile_session': request.session.profile_session,
46+
'profile_collectors': request.session.profile_collectors,
47+
'profile_params': request.session.profile_params,
4548
}
4649
if self.env.user.has_group('base.group_user'):
4750
# the following is only useful in the context of a webclient bootstrapping
@@ -86,6 +89,9 @@ def get_frontend_session_info(self):
8689
'is_website_user': request.session.uid and self.env.user._is_public() or False,
8790
'user_id': request.session.uid and self.env.user.id or False,
8891
'is_frontend': True,
92+
'profile_session': request.session.profile_session,
93+
'profile_collectors': request.session.profile_collectors,
94+
'profile_params': request.session.profile_params,
8995
}
9096
if request.session.uid:
9197
version_info = odoo.service.common.exp_version()

addons/web/static/src/js/tools/debug_manager.js

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,21 @@ var DebugManager = Widget.extend({
1616
xmlDependencies: ['/web/static/src/xml/debug.xml'],
1717
events: {
1818
"click a[data-action]": "perform_callback",
19+
"click .profiling": "handle_profiling",
20+
"change .profiling_param": "handle_profiling",
1921
},
2022
init: function () {
2123
this._super.apply(this, arguments);
2224
this._events = null;
2325
var debug = odoo.debug;
2426
this.debug_mode = debug;
2527
this.debug_mode_help = debug && debug !== '1' ? ' (' + debug + ')' : '';
28+
this.profile_session = odoo.session_info && odoo.session_info.profile_session || false;
29+
this.profile_collectors = odoo.session_info && odoo.session_info.profile_collectors;
30+
if (! Array.isArray(this.profile_collectors)) {
31+
this.profile_collectors = ['sql', 'traces_async']; // use default value when not defined in session.
32+
}
33+
this.profile_params = odoo.session_info && odoo.session_info.profile_params || {};
2634
},
2735
start: function () {
2836
core.bus.on('rpc:result', this, function (req, resp) {
@@ -53,7 +61,62 @@ var DebugManager = Widget.extend({
5361
console.warn("No handler for ", callback);
5462
}
5563
},
64+
/**
65+
* Manage profiling actions.
66+
* open_profiling_button should not prevent dropdown from closing
67+
* other actions should keep dropdown open and interact with backend to enable or disable profiling features.
68+
* The main switch, 'enable_profiling' is a special case since it will create or remove a profile session
69+
*/
70+
handle_profiling: async function (evt) {
71+
const $target = $(evt.target);
72+
const target = $target[0];
73+
if (target.id === 'open_profiling_button') {
74+
this.do_action('base.action_menu_ir_profile');
75+
return;
76+
}
77+
evt.stopPropagation(); // prevent dropdown from closing
78+
const kwargs = {
79+
params: {}
80+
};
81+
if (target.nodeName == "LABEL") {
82+
return
83+
}
84+
kwargs.profile = $('#enable_profiling')[0].checked;
85+
kwargs.collectors = $('input.profile_switch').toArray().filter(i => i.checked).map(i => i.id.replace(/^profile_/, ''));
86+
$('.profile_param').toArray().forEach(i => kwargs.params[i.id.replace(/^profile_/, '')] = i.value);
5687

88+
try {
89+
const resp = await this._rpc({
90+
model: 'ir.profile',
91+
method: 'set_profiling',
92+
kwargs: kwargs,
93+
});
94+
this.profile_session = resp.session;
95+
this.profile_collectors = resp.collectors;
96+
this.profile_params = resp.params;
97+
} catch (e) {
98+
this.profite_session = false;
99+
}
100+
this.update_profiling_state();
101+
},
102+
/**
103+
* Update the profiling dropdown menu state after any change to synchronyze server session and client
104+
* This is mainly usefull to avoid desync in case of multiple tabs
105+
*/
106+
update_profiling_state: function () {
107+
const self = this;
108+
this.$('#enable_profiling')[0].checked = Boolean(this.profile_session);
109+
this.$('.profile_switch').each(function () {
110+
this.checked = (self.profile_collectors.indexOf(this.id.replace(/^profile_/, '')) !== -1);
111+
const params = self.$('.' + this.id);
112+
params.toggleClass('d-none', !this.checked);
113+
params.each(function () {
114+
const params_input = $(this).find('.profile_param')[0];
115+
params_input.value = self.profile_params[params_input.id.replace(/^profile_/, '')] || '';
116+
});
117+
});
118+
this.$profiling_items.toggleClass('d-none', !this.profile_session);
119+
},
57120
_debug_events: function (events) {
58121
if (!this._events) {
59122
return;
@@ -73,6 +136,9 @@ var DebugManager = Widget.extend({
73136
.append(QWeb.render('WebClient.DebugManager.Global', {
74137
manager: this,
75138
}));
139+
this.$profiling_items = this.$(".profiling_items");
140+
141+
this.update_profiling_state();
76142
return Promise.resolve();
77143
},
78144
split_assets: function () {

addons/web/static/src/scss/debug_manager.scss

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@
55
margin: 2px 10px 2px 0;
66
float: left;
77
}
8+
89
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
2+
.o_debug_mode .fa { /* Active profiling red dot should be overed on the bottom right corner of the bug icon*/
3+
position: relative;
4+
.fa {
5+
position: absolute;
6+
bottom: -0.1em;
7+
right: -0.4em;
8+
font-size: 0.8em;
9+
}
10+
}
11+
12+
.open_profiling { /* Fix dropdown-item padding missing because of float: right*/
13+
padding: 3px;
14+
}

0 commit comments

Comments
 (0)