Dashboard graph optimization. Fixes #3941

pull/21/head
Aditya Toshniwal 2019-02-04 15:39:47 +05:30 committed by Akshay Joshi
parent dae8186c2a
commit aad7830d37
16 changed files with 538 additions and 286 deletions

View File

@ -50,5 +50,6 @@ Bug fixes
| `Bug #3929 <https://redmine.postgresql.org/issues/3929>`_ - Fix alignment of help messages in properties panels.
| `Bug #3932 <https://redmine.postgresql.org/issues/3932>`_ - Fix alignment of submenu for Internet Explorer.
| `Bug #3935 <https://redmine.postgresql.org/issues/3935>`_ - Ensure that grant wizard should list down functions for EPAS server running with no-redwood-compat mode.
| `Bug #3941 <https://redmine.postgresql.org/issues/3941>`_ - Dashboard graph optimization.
| `Bug #3954 <https://redmine.postgresql.org/issues/3954>`_ - Remove Python 2.6 code that's now obsolete.
| `Bug #3955 <https://redmine.postgresql.org/issues/3955>`_ - Expose the bind address in the Docker container via PGADMIN_BIND_ADDRESS.

View File

@ -9,7 +9,7 @@
"""A blueprint module implementing the dashboard frame."""
from functools import wraps
from flask import render_template, url_for, Response, g
from flask import render_template, url_for, Response, g, request
from flask_babelex import gettext
from flask_security import login_required
from pgadmin.utils import PgAdminModule
@ -154,21 +154,9 @@ class DashboardModule(PgAdminModule):
return [
'dashboard.index', 'dashboard.get_by_sever_id',
'dashboard.get_by_database_id',
'dashboard.session_stats',
'dashboard.get_session_stats_by_sever_id',
'dashboard.get_session_stats_by_database_id',
'dashboard.tps_stats',
'dashboard.tps_stats_by_server_id',
'dashboard.tps_stats_by_database_id',
'dashboard.ti_stats',
'dashboard.ti_stats_by_server_id',
'dashboard.ti_stats_by_database_id',
'dashboard.to_stats',
'dashboard.to_stats_by_server_id',
'dashboard.to_stats_by_database_id',
'dashboard.bio_stats',
'dashboard.bio_stats_by_server_id',
'dashboard.bio_stats_by_database_id',
'dashboard.dashboard_stats',
'dashboard.dashboard_stats_sid',
'dashboard.dashboard_stats_did',
'dashboard.activity',
'dashboard.get_activity_by_server_id',
'dashboard.get_activity_by_database_id',
@ -356,87 +344,36 @@ def get_data(sid, did, template):
)
@blueprint.route('/session_stats/', endpoint='session_stats')
@blueprint.route(
'/session_stats/<int:sid>', endpoint='get_session_stats_by_sever_id'
)
@blueprint.route(
'/session_stats/<int:sid>/<int:did>',
endpoint='get_session_stats_by_database_id'
)
@blueprint.route('/dashboard_stats',
endpoint='dashboard_stats')
@blueprint.route('/dashboard_stats/<int:sid>',
endpoint='dashboard_stats_sid')
@blueprint.route('/dashboard_stats/<int:sid>/<int:did>',
endpoint='dashboard_stats_did')
@login_required
@check_precondition
def session_stats(sid=None, did=None):
"""
This function returns server session statistics
:param sid: server id
:return:
"""
return get_data(sid, did, 'session_stats.sql')
def dashboard_stats(sid=None, did=None):
resp_data = {}
if request.args['chart_names'] != '':
chart_names = request.args['chart_names'].split(',')
@blueprint.route('/tps_stats/', endpoint='tps_stats')
@blueprint.route('/tps_stats/<int:sid>', endpoint='tps_stats_by_server_id')
@blueprint.route(
'/tps_stats/<int:sid>/<int:did>', endpoint='tps_stats_by_database_id'
)
@login_required
@check_precondition
def tps_stats(sid=None, did=None):
"""
This function returns server TPS throughput
:param sid: server id
:return:
"""
return get_data(sid, did, 'tps_stats.sql')
if not sid:
return internal_server_error(errormsg='Server ID not specified.')
sql = render_template(
"/".join([g.template_path, 'dashboard_stats.sql']), did=did,
chart_names=chart_names,
)
status, res = g.conn.execute_dict(sql)
@blueprint.route('/ti_stats/', endpoint='ti_stats')
@blueprint.route('/ti_stats/<int:sid>', endpoint='ti_stats_by_server_id')
@blueprint.route(
'/ti_stats/<int:sid>/<int:did>', endpoint='ti_stats_by_database_id'
)
@login_required
@check_precondition
def ti_stats(sid=None, did=None):
"""
This function returns server tuple input statistics
:param sid: server id
:return:
"""
return get_data(sid, did, 'ti_stats.sql')
for chart_row in res['rows']:
resp_data[chart_row['chart_name']] = chart_row['chart_data']
@blueprint.route('/to_stats/', endpoint='to_stats')
@blueprint.route('/to_stats/<int:sid>', endpoint='to_stats_by_server_id')
@blueprint.route(
'/to_stats/<int:sid>/<int:did>', endpoint='to_stats_by_database_id'
)
@login_required
@check_precondition
def to_stats(sid=None, did=None):
"""
This function returns server tuple output statistics
:param sid: server id
:return:
"""
return get_data(sid, did, 'to_stats.sql')
@blueprint.route('/bio_stats/', endpoint='bio_stats')
@blueprint.route('/bio_stats/<int:sid>', endpoint='bio_stats_by_server_id')
@blueprint.route(
'/bio_stats/<int:sid>/<int:did>', endpoint='bio_stats_by_database_id'
)
@login_required
@check_precondition
def bio_stats(sid=None, did=None):
"""
This function returns server block IO statistics
:param sid: server id
:return:
"""
return get_data(sid, did, 'bio_stats.sql')
return ajax_response(
response=resp_data,
status=200
)
@blueprint.route('/activity/', endpoint='activity')

View File

@ -68,7 +68,9 @@ export class Chart {
}
getOtherData(key) {
return this._otherData[key];
if(this._otherData[key]) {
return this._otherData[key];
}
}
setOtherData(key, value) {

View File

@ -10,11 +10,11 @@
define('pgadmin.dashboard', [
'sources/url_for', 'sources/gettext', 'require', 'jquery', 'underscore',
'sources/pgadmin', 'backbone', 'backgrid', './charting',
'pgadmin.alertifyjs', 'pgadmin.backform',
'sources/nodes/dashboard', 'pgadmin.browser', 'bootstrap', 'wcdocker',
'pgadmin.alertifyjs', 'pgadmin.backform', 'sources/nodes/dashboard',
'sources/utils', 'pgadmin.browser', 'bootstrap', 'wcdocker',
], function(
url_for, gettext, r, $, _, pgAdmin, Backbone, Backgrid, charting,
Alertify, Backform, NodesDashboard
Alertify, Backform, NodesDashboard, commonUtils
) {
pgAdmin.Browser = pgAdmin.Browser || {};
@ -214,8 +214,9 @@ define('pgadmin.dashboard', [
// Load the default welcome dashboard
var url = url_for('dashboard.index');
/* Store the chart objects and there interval ids in this store */
this.chartStore = {};
/* Store the chart objects, refresh freq and next refresh time */
this.chart_store = {};
this.charts_poller_int_id = -1;
var dashboardPanel = pgBrowser.panels['dashboard'].panel;
if (dashboardPanel) {
@ -373,96 +374,178 @@ define('pgadmin.dashboard', [
}
},
renderChartLoop: function(chartObj, sid, did, url, counter, refresh) {
var data = [],
dataset = [];
// Render the charts
renderCharts: function(charts_config) {
var theIntervalFunc = function() {
var path = url + sid;
if (did != -1) {
path += '/' + did;
let self = this,
tooltipFormatter = function(refresh, currVal) {
return(`Seconds ago: ${parseInt(currVal.x * refresh)}</br>
Value: ${currVal.y}`);
},
curr_epoch=commonUtils.getEpoch();
self.stopChartsPoller();
charts_config.map((chart_config) => {
if(self.chart_store[chart_config.chart_name]
&& self.old_preferences[chart_config.refresh_pref_name] !=
self.preferences[chart_config.refresh_pref_name]) {
self.clearChartFromStore(chart_config.chart_name);
}
if(self.chart_store[chart_config.chart_name]) {
let chart_obj = self.chart_store[chart_config.chart_name].chart_obj;
chart_obj.setOptions(chart_config.options, false);
chart_obj.setTooltipFormatter(
tooltipFormatter.bind(null, self.preferences[chart_config.refresh_pref_name])
);
}
if(!self.chart_store[chart_config.chart_name]) {
let chart_obj = new charting.Chart(chart_config.container, chart_config.options);
chart_obj.setTooltipFormatter(
tooltipFormatter.bind(null, self.preferences[chart_config.refresh_pref_name])
);
chart_obj.setOtherData('counter', chart_config.counter);
self.chart_store[chart_config.chart_name] = {
'chart_obj' : chart_obj,
'refresh_on': curr_epoch,
'refresh_rate': self.preferences[chart_config.refresh_pref_name],
};
}
});
self.startChartsPoller(self.chart_store, self.sid, self.did);
},
getStatsUrl: function(sid=-1, did=-1, chart_names=[]) {
let base_url = url_for('dashboard.dashboard_stats');
base_url += '/' + sid;
base_url += (did > 0) ? ('/' + did) : '';
base_url += '?chart_names=' + chart_names.join(',');
return base_url;
},
updateChart: function(chart_obj, new_data){
// Dataset format:
// [
// { data: [[0, y0], [1, y1]...], label: 'Label 1', [options] },
// { data: [[0, y0], [1, y1]...], label: 'Label 2', [options] },
// { data: [[0, y0], [1, y1]...], label: 'Label 3', [options] }
// ]
let dataset = chart_obj.getOtherData('dataset') || [],
counter_prev_data = chart_obj.getOtherData('counter_prev_data') || new_data,
counter = chart_obj.getOtherData('counter') || false;
if (dataset.length == 0) {
// Create the initial data structure
for (let label in new_data) {
dataset.push({
'data': [
[0, counter ? (new_data[label] - counter_prev_data[label]) : new_data[label]],
],
'label': label,
});
}
} else {
Object.keys(new_data).map((label, label_ind) => {
// Push new values onto the existing data structure
// If this is a counter stat, we need to subtract the previous value
if (!counter) {
dataset[label_ind]['data'].unshift([0, new_data[label]]);
} else {
// Store the current value, minus the previous one we stashed.
// It's possible the tab has been reloaded, in which case out previous values are gone
if (_.isUndefined(counter_prev_data))
return;
dataset[label_ind]['data'].unshift([0, new_data[label] - counter_prev_data[label]]);
}
// Reset the time index to get a proper scrolling display
for (var time_ind = 0; time_ind < dataset[label_ind]['data'].length; time_ind++) {
dataset[label_ind]['data'][time_ind][0] = time_ind;
}
});
counter_prev_data = new_data;
}
// Remove old data points
for (let label_ind = 0; label_ind < dataset.length; label_ind++) {
if (dataset[label_ind]['data'].length > 101) {
dataset[label_ind]['data'].pop();
}
}
chart_obj.setOtherData('dataset', dataset);
chart_obj.setOtherData('counter_prev_data', counter_prev_data);
if (chart_obj.isInPage()) {
if (chart_obj.isVisible()) {
chart_obj.draw(dataset);
}
} else {
return;
}
},
stopChartsPoller: function() {
clearInterval(this.charts_poller_int_id);
},
startChartsPoller: function(chart_store, sid, did) {
let self = this;
/* polling will the greatest common divisor of the refresh rates*/
let poll_interval = commonUtils.getGCD(
Object.values(chart_store).map(item => item.refresh_rate)
);
const WAIT_COUNTER = 3;
let last_poll_wait_counter = 0;
/* Stop if running, only one poller lives */
self.stopChartsPoller();
var thePollingFunc = function() {
let curr_epoch = commonUtils.getEpoch();
let chart_names_to_get = [];
for(let chart_name in chart_store) {
/* when its time to get the data */
if(chart_store[chart_name].refresh_on <= curr_epoch) {
/* set the next trigger point */
chart_store[chart_name].refresh_on = curr_epoch + chart_store[chart_name].refresh_rate;
chart_names_to_get.push(chart_name);
}
}
/* If none of the chart wants data, don't trouble
* If response not received from prev poll, don't trouble !!
*/
if(chart_names_to_get.length == 0 || last_poll_wait_counter > 0) {
/* reduce the number of tries, request should be sent if last_poll_wait_counter
* completes WAIT_COUNTER times.*/
last_poll_wait_counter--;
return;
}
var path = self.getStatsUrl(sid, did, chart_names_to_get);
$.ajax({
url: path,
type: 'GET',
dataType: 'html',
})
.done(function(resp) {
$(chartObj.getContainer()).removeClass('graph-error');
data = JSON.parse(resp);
var y = 0,
x;
if (dataset.length == 0) {
if (counter == true) {
// Have we stashed initial values?
if (_.isUndefined(chartObj.getOtherData('counter_previous_vals'))) {
chartObj.setOtherData('counter_previous_vals', data[0]);
} else {
// Create the initial data structure
for (x in data[0]) {
dataset.push({
'data': [
[0, data[0][x] - chartObj.getOtherData('counter_previous_vals')[x]],
],
'label': x,
});
}
}
} else {
// Create the initial data structure
for (x in data[0]) {
dataset.push({
'data': [
[0, data[0][x]],
],
'label': x,
});
}
}
} else {
for (x in data[0]) {
// Push new values onto the existing data structure
// If this is a counter stat, we need to subtract the previous value
if (counter == false) {
dataset[y]['data'].unshift([0, data[0][x]]);
} else {
// Store the current value, minus the previous one we stashed.
// It's possible the tab has been reloaded, in which case out previous values are gone
if (_.isUndefined(chartObj.getOtherData('counter_previous_vals')))
return;
dataset[y]['data'].unshift([0, data[0][x] - chartObj.getOtherData('counter_previous_vals')[x]]);
}
// Reset the time index to get a proper scrolling display
for (var z = 0; z < dataset[y]['data'].length; z++) {
dataset[y]['data'][z][0] = z;
}
y++;
}
chartObj.setOtherData('counter_previous_vals', data[0]);
last_poll_wait_counter = 0;
for(let chart_name in resp) {
let chart_obj = chart_store[chart_name].chart_obj;
$(chart_obj.getContainer()).removeClass('graph-error');
self.updateChart(chart_obj, resp[chart_name]);
}
// Remove uneeded elements
for (x = 0; x < dataset.length; x++) {
// Remove old data points
if (dataset[x]['data'].length > 101) {
dataset[x]['data'].pop();
}
}
if (chartObj.isInPage()) {
if (chartObj.isVisible()) {
chartObj.draw(dataset);
}
} else {
return;
}
})
.fail(function(xhr) {
last_poll_wait_counter = 0;
let err = '';
let msg = '';
let cls = 'info';
@ -484,66 +567,19 @@ define('pgadmin.dashboard', [
}
}
$(chartObj.getContainer()).addClass('graph-error');
$(chartObj.getContainer()).html(
'<div class="alert alert-' + cls + ' pg-panel-message" role="alert">' + msg + '</div>'
);
for(let chart_name in chart_store) {
let chart_obj = chart_store[chart_name].chart_obj;
$(chart_obj.getContainer()).addClass('graph-error');
$(chart_obj.getContainer()).html(
'<div class="alert alert-' + cls + ' pg-panel-message" role="alert">' + msg + '</div>'
);
}
});
last_poll_wait_counter = WAIT_COUNTER;
};
/* Execute once for the first time as setInterval will not do */
theIntervalFunc();
return setInterval(theIntervalFunc, refresh * 1000);
},
// Render a chart
render_chart: function(
container, url, options, counter, chartName, prefName
) {
// Data format:
// [
// { data: [[0, y0], [1, y1]...], label: 'Label 1', [options] },
// { data: [[0, y0], [1, y1]...], label: 'Label 2', [options] },
// { data: [[0, y0], [1, y1]...], label: 'Label 3', [options] }
// ]
let self = this,
tooltipFormatter = function(refresh, currVal) {
return(`Seconds ago: ${parseInt(currVal.x * refresh)}</br>
Value: ${currVal.y}`);
};
if(self.chartStore[chartName]
&& self.old_preferences[prefName] != self.preferences[prefName]) {
self.clearChartFromStore(chartName);
}
if(self.chartStore[chartName]) {
let chartObj = self.chartStore[chartName].chartObj;
chartObj.setOptions(options, false);
chartObj.setTooltipFormatter(
tooltipFormatter.bind(null, self.preferences[prefName])
);
}
if(!self.chartStore[chartName]) {
let chartObj = new charting.Chart(container, options);
chartObj.setTooltipFormatter(
tooltipFormatter.bind(null, self.preferences[prefName])
);
self.chartStore[chartName] = {
'chartObj' : chartObj,
'intervalId' : undefined,
};
self.chartStore[chartName]['intervalId'] = self.renderChartLoop(
self.chartStore[chartName]['chartObj'], self.sid, self.did, url,
counter, self.preferences[prefName]
);
}
thePollingFunc();
self.charts_poller_int_id = setInterval(thePollingFunc, poll_interval * 1000);
},
// Handler function to support the "Add Server" link
@ -683,14 +719,13 @@ define('pgadmin.dashboard', [
clearChartFromStore: function(chartName) {
var self = this;
if(!chartName){
_.each(self.chartStore, function(chart, key) {
clearInterval(chart.intervalId);
delete self.chartStore[key];
self.stopChartsPoller();
_.each(self.chart_store, function(chart, key) {
delete self.chart_store[key];
});
}
else {
clearInterval(self.chartStore[chartName].intervalId);
delete self.chartStore[chartName];
delete self.chart_store[chartName];
}
},
@ -797,26 +832,37 @@ define('pgadmin.dashboard', [
if(self.preferences.show_graphs) {
// Render the graphs
pgAdmin.Dashboard.render_chart(
div_sessions, url_for('dashboard.session_stats'), options_line, false,
'session_stats', 'session_stats_refresh'
);
pgAdmin.Dashboard.render_chart(
div_tps, url_for('dashboard.tps_stats'), options_line, true,
'tps_stats','tps_stats_refresh'
);
pgAdmin.Dashboard.render_chart(
div_ti, url_for('dashboard.ti_stats'), options_line, true,
'ti_stats', 'ti_stats_refresh'
);
pgAdmin.Dashboard.render_chart(
div_to, url_for('dashboard.to_stats'), options_line, true,
'to_stats','to_stats_refresh'
);
pgAdmin.Dashboard.render_chart(
div_bio, url_for('dashboard.bio_stats'), options_line, true,
'bio_stats','bio_stats_refresh'
);
pgAdmin.Dashboard.renderCharts([{
chart_name: 'session_stats',
container: div_sessions,
options: options_line,
counter: false,
refresh_pref_name: 'session_stats_refresh',
}, {
chart_name: 'tps_stats',
container: div_tps,
options: options_line,
counter: true,
refresh_pref_name: 'tps_stats_refresh',
}, {
chart_name: 'ti_stats',
container: div_ti,
options: options_line,
counter: true,
refresh_pref_name: 'ti_stats_refresh',
}, {
chart_name: 'to_stats',
container: div_to,
options: options_line,
counter: true,
refresh_pref_name: 'to_stats_refresh',
}, {
chart_name: 'bio_stats',
container: div_bio,
options: options_line,
counter: true,
refresh_pref_name: 'bio_stats_refresh',
}]);
}
if(!self.preferences.show_graphs && !self.preferences.show_activity) {

View File

@ -1,4 +0,0 @@
/*pga4dash*/
SELECT
(SELECT sum(blks_read) FROM pg_stat_database{% if did %} WHERE datname = (SELECT datname FROM pg_database WHERE oid = {{ did }}){% endif %}) AS "{{ _('Reads') }}",
(SELECT sum(blks_hit) FROM pg_stat_database{% if did %} WHERE datname = (SELECT datname FROM pg_database WHERE oid = {{ did }}){% endif %}) AS "{{ _('Hits') }}"

View File

@ -0,0 +1,56 @@
{% set add_union = false %}
{% if 'session_stats' in chart_names %}
{% set add_union = true %}
SELECT 'session_stats' AS chart_name, row_to_json(t) AS chart_data
FROM (SELECT
(SELECT count(*) FROM pg_stat_activity{% if did %} WHERE datname = (SELECT datname FROM pg_database WHERE oid = {{ did }}){% endif %}) AS "{{ _('Total') }}",
(SELECT count(*) FROM pg_stat_activity WHERE state = 'active'{% if did %} AND datname = (SELECT datname FROM pg_database WHERE oid = {{ did }}){% endif %}) AS "{{ _('Active') }}",
(SELECT count(*) FROM pg_stat_activity WHERE state = 'idle'{% if did %} AND datname = (SELECT datname FROM pg_database WHERE oid = {{ did }}){% endif %}) AS "{{ _('Idle') }}"
) t
{% endif %}
{% if add_union and 'tps_stats' in chart_names %}
UNION ALL
{% endif %}
{% if 'tps_stats' in chart_names %}
{% set add_union = true %}
SELECT 'tps_stats' AS chart_name, row_to_json(t) AS chart_data
FROM (SELECT
(SELECT sum(xact_commit) + sum(xact_rollback) FROM pg_stat_database{% if did %} WHERE datname = (SELECT datname FROM pg_database WHERE oid = {{ did }}){% endif %}) AS "{{ _('Transactions') }}",
(SELECT sum(xact_commit) FROM pg_stat_database{% if did %} WHERE datname = (SELECT datname FROM pg_database WHERE oid = {{ did }}){% endif %}) AS "{{ _('Commits') }}",
(SELECT sum(xact_rollback) FROM pg_stat_database{% if did %} WHERE datname = (SELECT datname FROM pg_database WHERE oid = {{ did }}){% endif %}) AS "{{ _('Rollbacks') }}"
) t
{% endif %}
{% if add_union and 'ti_stats' in chart_names %}
UNION ALL
{% endif %}
{% if 'ti_stats' in chart_names %}
{% set add_union = true %}
SELECT 'ti_stats' AS chart_name, row_to_json(t) AS chart_data
FROM (SELECT
(SELECT sum(tup_inserted) FROM pg_stat_database{% if did %} WHERE datname = (SELECT datname FROM pg_database WHERE oid = {{ did }}){% endif %}) AS "{{ _('Inserts') }}",
(SELECT sum(tup_updated) FROM pg_stat_database{% if did %} WHERE datname = (SELECT datname FROM pg_database WHERE oid = {{ did }}){% endif %}) AS "{{ _('Updates') }}",
(SELECT sum(tup_deleted) FROM pg_stat_database{% if did %} WHERE datname = (SELECT datname FROM pg_database WHERE oid = {{ did }}){% endif %}) AS "{{ _('Deletes') }}"
) t
{% endif %}
{% if add_union and 'to_stats' in chart_names %}
UNION ALL
{% endif %}
{% if 'to_stats' in chart_names %}
{% set add_union = true %}
SELECT 'to_stats' AS chart_name, row_to_json(t) AS chart_data
FROM (SELECT
(SELECT sum(tup_fetched) FROM pg_stat_database{% if did %} WHERE datname = (SELECT datname FROM pg_database WHERE oid = {{ did }}){% endif %}) AS "{{ _('Fetched') }}",
(SELECT sum(tup_returned) FROM pg_stat_database{% if did %} WHERE datname = (SELECT datname FROM pg_database WHERE oid = {{ did }}){% endif %}) AS "{{ _('Returned') }}"
) t
{% endif %}
{% if add_union and 'bio_stats' in chart_names %}
UNION ALL
{% endif %}
{% if 'bio_stats' in chart_names %}
{% set add_union = true %}
SELECT 'bio_stats' AS chart_name, row_to_json(t) AS chart_data
FROM (SELECT
(SELECT sum(blks_read) FROM pg_stat_database{% if did %} WHERE datname = (SELECT datname FROM pg_database WHERE oid = {{ did }}){% endif %}) AS "{{ _('Reads') }}",
(SELECT sum(blks_hit) FROM pg_stat_database{% if did %} WHERE datname = (SELECT datname FROM pg_database WHERE oid = {{ did }}){% endif %}) AS "{{ _('Hits') }}"
) t
{% endif %}

View File

@ -1,5 +0,0 @@
/*pga4dash*/
SELECT
(SELECT count(*) FROM pg_stat_activity{% if did %} WHERE datname = (SELECT datname FROM pg_database WHERE oid = {{ did }}){% endif %}) AS "{{ _('Total') }}",
(SELECT count(*) FROM pg_stat_activity WHERE state = 'active'{% if did %} AND datname = (SELECT datname FROM pg_database WHERE oid = {{ did }}){% endif %}) AS "{{ _('Active') }}",
(SELECT count(*) FROM pg_stat_activity WHERE state = 'idle'{% if did %} AND datname = (SELECT datname FROM pg_database WHERE oid = {{ did }}){% endif %}) AS "{{ _('Idle') }}"

View File

@ -1,5 +0,0 @@
/*pga4dash*/
SELECT
(SELECT sum(tup_inserted) FROM pg_stat_database{% if did %} WHERE datname = (SELECT datname FROM pg_database WHERE oid = {{ did }}){% endif %}) AS "{{ _('Inserts') }}",
(SELECT sum(tup_updated) FROM pg_stat_database{% if did %} WHERE datname = (SELECT datname FROM pg_database WHERE oid = {{ did }}){% endif %}) AS "{{ _('Updates') }}",
(SELECT sum(tup_deleted) FROM pg_stat_database{% if did %} WHERE datname = (SELECT datname FROM pg_database WHERE oid = {{ did }}){% endif %}) AS "{{ _('Deletes') }}"

View File

@ -1,4 +0,0 @@
/*pga4dash*/
SELECT
(SELECT sum(tup_fetched) FROM pg_stat_database{% if did %} WHERE datname = (SELECT datname FROM pg_database WHERE oid = {{ did }}){% endif %}) AS "{{ _('Fetched') }}",
(SELECT sum(tup_returned) FROM pg_stat_database{% if did %} WHERE datname = (SELECT datname FROM pg_database WHERE oid = {{ did }}){% endif %}) AS "{{ _('Returned') }}"

View File

@ -1,5 +0,0 @@
/*pga4dash*/
SELECT
(SELECT sum(xact_commit) + sum(xact_rollback) FROM pg_stat_database{% if did %} WHERE datname = (SELECT datname FROM pg_database WHERE oid = {{ did }}){% endif %}) AS "{{ _('Transactions') }}",
(SELECT sum(xact_commit) FROM pg_stat_database{% if did %} WHERE datname = (SELECT datname FROM pg_database WHERE oid = {{ did }}){% endif %}) AS "{{ _('Commits') }}",
(SELECT sum(xact_rollback) FROM pg_stat_database{% if did %} WHERE datname = (SELECT datname FROM pg_database WHERE oid = {{ did }}){% endif %}) AS "{{ _('Rollbacks') }}"

View File

@ -0,0 +1,56 @@
{% set add_union = false %}
{% if 'session_stats' in chart_names %}
{% set add_union = true %}
SELECT 'session_stats' AS chart_name, row_to_json(t) AS chart_data
FROM (SELECT
(SELECT count(*) FROM pg_stat_activity{% if did %} WHERE datid = {{ did }} {% endif %}) AS "{{ _('Total') }}",
(SELECT count(*) FROM pg_stat_activity WHERE current_query NOT LIKE '<IDLE>%'{% if did %} AND datid = {{ did }} {% endif %}) AS "{{ _('Active') }}",
(SELECT count(*) FROM pg_stat_activity WHERE current_query LIKE '<IDLE>%'{% if did %} AND datid = {{ did }} {% endif %}) AS "{{ _('Idle') }}"
) t
{% endif %}
{% if add_union and 'tps_stats' in chart_names %}
UNION ALL
{% endif %}
{% if 'tps_stats' in chart_names %}
{% set add_union = true %}
SELECT 'tps_stats' AS chart_name, row_to_json(t) AS chart_data
FROM (SELECT
(SELECT sum(xact_commit) + sum(xact_rollback) FROM pg_stat_database{% if did %} WHERE datname = (SELECT datname FROM pg_database WHERE oid = {{ did }}){% endif %}) AS "{{ _('Transactions') }}",
(SELECT sum(xact_commit) FROM pg_stat_database{% if did %} WHERE datname = (SELECT datname FROM pg_database WHERE oid = {{ did }}){% endif %}) AS "{{ _('Commits') }}",
(SELECT sum(xact_rollback) FROM pg_stat_database{% if did %} WHERE datname = (SELECT datname FROM pg_database WHERE oid = {{ did }}){% endif %}) AS "{{ _('Rollbacks') }}"
) t
{% endif %}
{% if add_union and 'ti_stats' in chart_names %}
UNION ALL
{% endif %}
{% if 'ti_stats' in chart_names %}
{% set add_union = true %}
SELECT 'ti_stats' AS chart_name, row_to_json(t) AS chart_data
FROM (SELECT
(SELECT sum(tup_inserted) FROM pg_stat_database{% if did %} WHERE datname = (SELECT datname FROM pg_database WHERE oid = {{ did }}){% endif %}) AS "{{ _('Inserts') }}",
(SELECT sum(tup_updated) FROM pg_stat_database{% if did %} WHERE datname = (SELECT datname FROM pg_database WHERE oid = {{ did }}){% endif %}) AS "{{ _('Updates') }}",
(SELECT sum(tup_deleted) FROM pg_stat_database{% if did %} WHERE datname = (SELECT datname FROM pg_database WHERE oid = {{ did }}){% endif %}) AS "{{ _('Deletes') }}"
) t
{% endif %}
{% if add_union and 'to_stats' in chart_names %}
UNION ALL
{% endif %}
{% if 'to_stats' in chart_names %}
{% set add_union = true %}
SELECT 'to_stats' AS chart_name, row_to_json(t) AS chart_data
FROM (SELECT
(SELECT sum(tup_fetched) FROM pg_stat_database{% if did %} WHERE datname = (SELECT datname FROM pg_database WHERE oid = {{ did }}){% endif %}) AS "{{ _('Fetched') }}",
(SELECT sum(tup_returned) FROM pg_stat_database{% if did %} WHERE datname = (SELECT datname FROM pg_database WHERE oid = {{ did }}){% endif %}) AS "{{ _('Returned') }}"
) t
{% endif %}
{% if add_union and 'bio_stats' in chart_names %}
UNION ALL
{% endif %}
{% if 'bio_stats' in chart_names %}
{% set add_union = true %}
SELECT 'bio_stats' AS chart_name, row_to_json(t) AS chart_data
FROM (SELECT
(SELECT sum(blks_read) FROM pg_stat_database{% if did %} WHERE datname = (SELECT datname FROM pg_database WHERE oid = {{ did }}){% endif %}) AS "{{ _('Reads') }}",
(SELECT sum(blks_hit) FROM pg_stat_database{% if did %} WHERE datname = (SELECT datname FROM pg_database WHERE oid = {{ did }}){% endif %}) AS "{{ _('Hits') }}"
) t
{% endif %}

View File

@ -1,5 +0,0 @@
/*pga4dash*/
SELECT
(SELECT count(*) FROM pg_stat_activity{% if did %} WHERE datid = {{ did }} {% endif %}) AS "{{ _('Total') }}",
(SELECT count(*) FROM pg_stat_activity WHERE current_query NOT LIKE '<IDLE>%'{% if did %} AND datid = {{ did }} {% endif %}) AS "{{ _('Active') }}",
(SELECT count(*) FROM pg_stat_activity WHERE current_query LIKE '<IDLE>%'{% if did %} AND datid = {{ did }} {% endif %}) AS "{{ _('Idle') }}"

View File

@ -0,0 +1,119 @@
##########################################################################
#
# pgAdmin 4 - PostgreSQL Tools
#
# Copyright (C) 2013 - 2019, The pgAdmin Development Team
# This software is released under the PostgreSQL Licence
#
##########################################################################
from pgadmin.utils.route import BaseTestGenerator
from pgadmin.utils import server_utils as server_utils
import simplejson as json
class DashboardGraphsTestCase(BaseTestGenerator):
"""
This class validates the version in range functionality
by defining different version scenarios; where dict of
parameters describes the scenario appended by test name.
"""
scenarios = [(
'TestCase for session_stats graph', dict(
sid=1,
did=-1,
chart_data={
'session_stats': ['Total', 'Active', 'Idle'],
}
)), (
'TestCase for tps_stats graph', dict(
sid=1,
did=-1,
chart_data={
'tps_stats': ['Transactions', 'Commits', 'Rollbacks'],
}
)), (
'TestCase for ti_stats graph', dict(
sid=1,
did=-1,
chart_data={
'ti_stats': ['Inserts', 'Updates', 'Deletes'],
}
)), (
'TestCase for to_stats graph', dict(
sid=1,
did=-1,
chart_data={
'to_stats': ['Fetched', 'Returned'],
}
)), (
'TestCase for bio_stats graph', dict(
sid=1,
did=-1,
chart_data={
'bio_stats': ['Reads', 'Hits'],
}
)), (
'TestCase for two graphs', dict(
sid=1,
did=-1,
chart_data={
'session_stats': ['Total', 'Active', 'Idle'],
'bio_stats': ['Reads', 'Hits'],
}
)), (
'TestCase for five graphs', dict(
sid=1,
did=-1,
chart_data={
'session_stats': ['Total', 'Active', 'Idle'],
'tps_stats': ['Transactions', 'Commits', 'Rollbacks'],
'ti_stats': ['Inserts', 'Updates', 'Deletes'],
'to_stats': ['Fetched', 'Returned'],
'bio_stats': ['Reads', 'Hits'],
}
)), (
'TestCase for no graph', dict(
sid=1,
did=-1,
chart_data={},
))
]
def setUp(self):
pass
def getStatsUrl(self, sid=-1, did=-1, chart_names=''):
base_url = '/dashboard/dashboard_stats'
base_url = base_url + '/' + str(sid)
base_url += '/' + str(did) if did > 0 else ''
base_url += '?chart_names=' + chart_names
return base_url
def runTest(self):
server_response = server_utils.connect_server(self, self.sid)
if server_response["info"] == "Server connected.":
url = self.getStatsUrl(self.sid, self.did,
",".join(self.chart_data.keys()))
response = self.tester.get(url)
self.assertEquals(response.status_code, 200)
resp_data = json.loads(response.data)
# All requested charts received
self.assertEquals(len(resp_data.keys()),
len(self.chart_data.keys()))
# All requested charts data received
for chart_name, chart_vals in self.chart_data.items():
self.assertEquals(set(resp_data[chart_name].keys()),
set(chart_vals))
else:
raise Exception("Error while connecting server to add the"
" database.")
def tearDown(self):
pass

View File

@ -46,3 +46,27 @@ let isString = (str) => (_.isString(str));
export {
isValidData, isFunction, isString,
};
export function getEpoch(inp_date) {
let date_obj = inp_date ? inp_date : new Date();
return parseInt(date_obj.getTime()/1000);
}
/* Eucladian GCD */
export function getGCD(inp_arr) {
let gcd_for_two = (a, b) => {
return a == 0?b:gcd_for_two(b % a, a);
};
let inp_len = inp_arr.length;
if(inp_len <= 2) {
return gcd_for_two(inp_arr[0], inp_arr[1]);
}
let result = inp_arr[0];
for(let i=1; i<inp_len; i++) {
result = gcd_for_two(inp_arr[i], result);
}
return result;
}

View File

@ -73,6 +73,10 @@ describe('In charting related testcases', ()=> {
expect(chartObj.getOtherData('some_val')).toEqual(1);
});
it('Check if other data returns undefined for not set', ()=>{
expect(chartObj.getOtherData('some_val_not_set')).toBe(undefined);
});
it('Check if isVisible returns correct', ()=>{
let dimSpy = spyOn(chartObj, 'getContainerDimensions');

View File

@ -0,0 +1,35 @@
/////////////////////////////////////////////////////////////
//
// pgAdmin 4 - PostgreSQL Tools
//
// Copyright (C) 2013 - 2019, The pgAdmin Development Team
// This software is released under the PostgreSQL Licence
//
//////////////////////////////////////////////////////////////
import { getEpoch, getGCD } from 'sources/utils';
describe('getEpoch', function () {
it('should return non zero', function () {
expect(getEpoch()).toBeGreaterThan(0);
});
it('should return epoch for a date passed', function () {
let someDate = new Date(2019,1,1,10,20,30,40),
someDateEpoch = 1548996630;
expect(getEpoch(new Date(someDate))).toEqual(someDateEpoch);
});
});
describe('getGCD', function () {
it('for two numbers', function () {
let nos = [5, 10];
expect(getGCD(nos)).toEqual(5);
});
it('for more than two numbers', function () {
let nos = [9, 24, 33];
expect(getGCD(nos)).toEqual(3);
});
});