Highlighted long running queries on the dashboards. Fixes #1975

This commit is contained in:
Akshay Joshi 2021-07-01 18:10:45 +05:30
parent d90472014d
commit ef67409d61
12 changed files with 147 additions and 6 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 135 KiB

After

Width:  |  Height:  |  Size: 156 KiB

View File

@ -154,6 +154,9 @@ the graphs on the *Dashboard* tab:
:alt: Preferences dialog dashboard display options
:align: center
* Set the warning and alert threshold value to highlight the long-running
queries on the dashboard.
* When the *Show activity?* switch is set to *True*, activity tables will be
displayed on dashboards.

View File

@ -9,6 +9,7 @@ This release contains a number of bug fixes and new features since the release o
New features
************
| `Issue #1975 <https://redmine.postgresql.org/issues/1975>`_ - Highlighted long running queries on the dashboards.
| `Issue #3893 <https://redmine.postgresql.org/issues/3893>`_ - Added support for Reassign/Drop Owned for login roles.
| `Issue #3920 <https://redmine.postgresql.org/issues/3920>`_ - Do not block the query editor window when running a query.
| `Issue #6559 <https://redmine.postgresql.org/issues/6559>`_ - Added option to provide maximum width of the column when 'Resize by data? option in the preferences is set to True.

View File

@ -8,6 +8,7 @@
##########################################################################
"""A blueprint module implementing the dashboard frame."""
import math
from functools import wraps
from flask import render_template, url_for, Response, g, request
from flask_babelex import gettext
@ -150,6 +151,15 @@ class DashboardModule(PgAdminModule):
'details')
)
self.long_running_query_threshold = self.dashboard_preference.register(
'display', 'long_running_query_threshold',
gettext('Long running query thresholds'), 'threshold',
None, category_label=PREF_LABEL_DISPLAY,
help_str=gettext('Set the warning and alert threshold value to '
'highlight the long-running queries on the '
'dashboard.')
)
def get_exposed_url_endpoints(self):
"""
Returns:
@ -300,13 +310,14 @@ def index(sid=None, did=None):
)
def get_data(sid, did, template):
def get_data(sid, did, template, check_long_running_query=False):
"""
Generic function to get server stats based on an SQL template
Args:
sid: The server ID
did: The database ID
template: The SQL template name
check_long_running_query:
Returns:
@ -324,12 +335,47 @@ def get_data(sid, did, template):
if not status:
return internal_server_error(errormsg=res)
# Check the long running query status and set the row type.
if check_long_running_query:
get_long_running_query_status(res['rows'])
return ajax_response(
response=res['rows'],
status=200
)
def get_long_running_query_status(activities):
"""
This function is used to check the long running query and set the
row type to highlight the row color accordingly
"""
dash_preference = Preferences.module('dashboards')
long_running_query_threshold = \
dash_preference.preference('long_running_query_threshold').get()
if long_running_query_threshold is not None:
long_running_query_threshold = long_running_query_threshold.split('|')
warning_value = float(long_running_query_threshold[0]) \
if long_running_query_threshold[0] != '' else math.inf
alert_value = float(long_running_query_threshold[1]) \
if long_running_query_threshold[1] != '' else math.inf
for row in activities:
row['row_type'] = None
# We care for only those queries which are in active state.
if row['state'] != 'active':
continue
active_since = float(row['active_since'])
if active_since > warning_value:
row['row_type'] = 'warning'
if active_since > alert_value:
row['row_type'] = 'alert'
@blueprint.route('/dashboard_stats',
endpoint='dashboard_stats')
@blueprint.route('/dashboard_stats/<int:sid>',
@ -376,7 +422,7 @@ def activity(sid=None, did=None):
:param sid: server id
:return:
"""
return get_data(sid, did, 'activity.sql')
return get_data(sid, did, 'activity.sql', True)
@blueprint.route('/locks/', endpoint='locks')

View File

@ -437,12 +437,31 @@ define('pgadmin.dashboard', [
var data = new Data();
var HighlightedRow = Backgrid.Row.extend({
render: function() {
Backgrid.Row.prototype.render.call(this);
var row_type = this.model.get('row_type');
if (_.isUndefined(row_type) || _.isNull(row_type)) {
this.$el.removeClass('alert');
this.$el.removeClass('warning');
} else if (row_type === 'warning') {
this.$el.addClass('warning');
} else if (row_type === 'alert') {
this.$el.addClass('alert');
}
return this;
}
});
// Set up the grid
var grid = new Backgrid.Grid({
emptyText: gettext('No data found'),
columns: columns,
collection: data,
className: 'backgrid presentation table table-bordered table-noouter-border table-hover',
row: HighlightedRow
});
// Render the grid

View File

@ -12,7 +12,8 @@ SELECT
query,
pg_catalog.to_char(state_change, 'YYYY-MM-DD HH24:MI:SS TZ') AS state_change,
pg_catalog.to_char(query_start, 'YYYY-MM-DD HH24:MI:SS TZ') AS query_start,
backend_type
backend_type,
CASE WHEN state = 'active' THEN ROUND((extract(epoch from now() - query_start) / 60)::numeric, 2) ELSE 0 END AS active_since
FROM
pg_catalog.pg_stat_activity
{% if did %}WHERE

View File

@ -11,7 +11,8 @@ SELECT
pg_catalog.pg_blocking_pids(pid) AS blocking_pids,
query,
pg_catalog.to_char(state_change, 'YYYY-MM-DD HH24:MI:SS TZ') AS state_change,
pg_catalog.to_char(query_start, 'YYYY-MM-DD HH24:MI:SS TZ') AS query_start
pg_catalog.to_char(query_start, 'YYYY-MM-DD HH24:MI:SS TZ') AS query_start,
CASE WHEN state = 'active' THEN ROUND((extract(epoch from now() - query_start) / 60)::numeric, 2) ELSE 0 END AS active_since
FROM
pg_catalog.pg_stat_activity
{% if did %}WHERE

View File

@ -10,7 +10,8 @@ SELECT
CASE WHEN waiting THEN '{{ _('yes') }}' ELSE '{{ _('no') }}' END AS waiting,
query,
pg_catalog.to_char(state_change, 'YYYY-MM-DD HH24:MI:SS TZ') AS state_change,
pg_catalog.to_char(query_start, 'YYYY-MM-DD HH24:MI:SS TZ') AS query_start
pg_catalog.to_char(query_start, 'YYYY-MM-DD HH24:MI:SS TZ') AS query_start,
CASE WHEN state = 'active' THEN ROUND((extract(epoch from now() - query_start) / 60)::numeric, 2) ELSE 0 END AS active_since
FROM
pg_catalog.pg_stat_activity
{% if did %}WHERE

View File

@ -309,6 +309,11 @@ define('pgadmin.preferences', [
return 'radioModern';
case 'selectFile':
return 'binary-paths-grid';
case 'threshold':
p.warning_label = gettext('Warning');
p.alert_label = gettext('Alert');
p.unit = gettext('(in minutes)');
return 'threshold';
default:
if (console && console.warn) {
// Warning for developer only.

View File

@ -3584,5 +3584,61 @@ define([
},
});
Backform.ThresholdControl = Backform.Control.extend({
template: _.template([
'<label class="<%=Backform.controlLabelClassName%>"><%=label%></label>',
'<div class="<%=Backform.controlContainerClassName%>">',
' <span class="control-label pg-el-sm-2 pg-el-12"><%=warning_label%></span>',
' <input type="text" id="warning_threshold" class="pg-el-sm-2" value="<%=warning_value%>" />',
' <span class="control-label pg-el-sm-1 pg-el-12"><%=alert_label%></span>',
' <input type="text" id="alert_threshold" class="pg-el-sm-2" value="<%=alert_value%>" />',
' <span class="control-label pg-el-sm-3 pg-el-12"><%=unit%></span>',
' <% if (helpMessage && helpMessage.length) { %>',
' <span class="<%=Backform.helpMessageClassName%>"><%=helpMessage%></span>',
' <% } %>',
'</div>',
].join('\n')),
events: {
'change input#warning_threshold': 'onChange',
'change input#alert_threshold': 'onChange',
},
initialize: function() {
Backform.Control.prototype.initialize.apply(this, arguments);
},
render: function() {
var field = _.defaults(this.field.toJSON(), this.defaults),
attributes = this.model.toJSON(),
threshold_val = [];
if (!_.isUndefined(this.model.get('value')) && !_.isNull(this.model.get('value'))){
threshold_val = this.model.get('value').split('|');
}
var data = _.extend(field, {
attributes: attributes,
'warning_value': threshold_val.length > 0 ? threshold_val[0] : '',
'alert_value': threshold_val.length > 1 ? threshold_val[1] : ''
});
this.$el.html(this.template(data));
return this;
},
onChange: function() {
// Get the value from raw jquery and concat it using |
// and set the value
var warning_threshold = $('input#warning_threshold').val(),
alert_threshold = $('input#alert_threshold').val();
var threshold_val = warning_threshold + '|' + alert_threshold;
this.model.set(this.field.get('name'), threshold_val, {
silent: false
});
}
});
return Backform;
});

View File

@ -339,6 +339,14 @@ table.backgrid {
border-bottom-color: $color-gray-light !important;
}
}
tr.warning {
background-color: $color-warning !important;
}
tr.alert {
background-color: $color-danger-light !important;
}
}
table tr th button {

View File

@ -437,7 +437,7 @@ class Preferences(object):
assert _type in (
'boolean', 'integer', 'numeric', 'date', 'datetime',
'options', 'multiline', 'switch', 'node', 'text', 'radioModern',
'keyboardshortcut', 'select2', 'selectFile',
'keyboardshortcut', 'select2', 'selectFile', 'threshold'
), "Type cannot be found in the defined list!"
(cat['preferences'])[name] = res = _Preference(