Hi,
Please find the attached System Stats extension patch.
As my Final Submission for GSoC.
Thanks,
Kunal
diff --git a/web/pgadmin/dashboard/__init__.py b/web/pgadmin/dashboard/__init__.py
index d13b8bdeb..4d9db7f03 100644
--- a/web/pgadmin/dashboard/__init__.py
+++ b/web/pgadmin/dashboard/__init__.py
@@ -10,7 +10,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 import render_template, url_for, Response, g, request, current_app
from flask_babel import gettext
from flask_security import login_required
import simplejson as json
@@ -71,7 +71,6 @@ class DashboardModule(PgAdminModule):
self.dashboard_preference = Preferences(
'dashboards', gettext('Dashboards')
)
-
self.session_stats_refresh = self.dashboard_preference.register(
'dashboards', 'session_stats_refresh',
gettext("Session statistics refresh rate"), 'integer',
@@ -79,7 +78,6 @@ class DashboardModule(PgAdminModule):
category_label=PREF_LABEL_REFRESH_RATES,
help_str=help_string
)
-
self.tps_stats_refresh = self.dashboard_preference.register(
'dashboards', 'tps_stats_refresh',
gettext("Transaction throughput refresh rate"), 'integer',
@@ -87,7 +85,6 @@ class DashboardModule(PgAdminModule):
category_label=PREF_LABEL_REFRESH_RATES,
help_str=help_string
)
-
self.ti_stats_refresh = self.dashboard_preference.register(
'dashboards', 'ti_stats_refresh',
gettext("Tuples in refresh rate"), 'integer',
@@ -95,7 +92,6 @@ class DashboardModule(PgAdminModule):
category_label=PREF_LABEL_REFRESH_RATES,
help_str=help_string
)
-
self.to_stats_refresh = self.dashboard_preference.register(
'dashboards', 'to_stats_refresh',
gettext("Tuples out refresh rate"), 'integer',
@@ -111,7 +107,52 @@ class DashboardModule(PgAdminModule):
category_label=PREF_LABEL_REFRESH_RATES,
help_str=help_string
)
+ self.thread_activity_refresh = self.dashboard_preference.register(
+ 'dashboards', 'thread_activity_refresh',
+ gettext("Thread activity refresh rate"), 'integer',
+ 1, min_val=1, max_val=999999,
+ category_label=PREF_LABEL_REFRESH_RATES,
+ help_str=help_string
+ )
+
+ self.cpu_activity_refresh = self.dashboard_preference.register(
+ 'dashboards', 'cpu_activity_refresh',
+ gettext("CPU activity refresh rate"), 'integer',
+ 1, min_val=1, max_val=999999,
+ category_label=PREF_LABEL_REFRESH_RATES,
+ help_str=help_string
+ )
+
+ self.memory_comp_refresh = self.dashboard_preference.register(
+ 'dashboards', 'memory_comp_refresh',
+ gettext("Memory composition refresh rate"), 'integer',
+ 1, min_val=1, max_val=999999,
+ category_label=PREF_LABEL_REFRESH_RATES,
+ help_str=help_string
+ )
+
+ self.disk_info_refresh = self.dashboard_preference.register(
+ 'dashboards', 'disk_info_refresh',
+ gettext("Disk Information refresh rate"), 'integer',
+ 1, min_val=1, max_val=999999,
+ category_label=PREF_LABEL_REFRESH_RATES,
+ help_str=help_string
+ )
+ self.avg_load_refresh = self.dashboard_preference.register(
+ 'dashboards', 'avg_load_refresh',
+ gettext("Average load refresh rate"), 'integer',
+ 1, min_val=1, max_val=999999,
+ category_label=PREF_LABEL_REFRESH_RATES,
+ help_str=help_string
+ )
+ self.nics_refresh = self.dashboard_preference.register(
+ 'dashboards', 'nics_refresh',
+ gettext("NICs refresh rate"), 'integer',
+ 1, min_val=1, max_val=999999,
+ category_label=PREF_LABEL_REFRESH_RATES,
+ help_str=help_string
+ )
self.display_graphs = self.dashboard_preference.register(
'display', 'show_graphs',
gettext("Show graphs?"), 'boolean', True,
@@ -197,6 +238,18 @@ class DashboardModule(PgAdminModule):
'dashboard.get_prepared_by_database_id',
'dashboard.config',
'dashboard.get_config_by_server_id',
+ 'dashboard.cpu_activity',
+ 'dashboard.get_cpu_activity_by_server_id',
+ 'dashboard.get_cpu_activity_by_database_id',
+ 'dashboard.os_activity',
+ 'dashboard.get_os_activity_by_server_id',
+ 'dashboard.get_os_activity_by_database_id',
+ 'dashboard.memory_activity',
+ 'dashboard.get_memory_activity_by_server_id',
+ 'dashboard.get_memory_activity_by_database_id',
+ 'dashboard.system_stats',
+ 'dashboard.system_stats_sid',
+ 'dashboard.system_stats_did',
]
@@ -303,25 +356,12 @@ def index(sid=None, did=None):
if not g.conn.connected():
g.version = 0
-
- # Show the appropriate dashboard based on the identifiers passed to us
- if sid is None and did is None:
- return render_template('/dashboard/welcome_dashboard.html')
- if did is None:
- return render_template(
- '/dashboard/server_dashboard.html',
- sid=sid,
- rates=rates,
- version=g.version
- )
+ # To check system stats extension
else:
- return render_template(
- '/dashboard/database_dashboard.html',
- sid=sid,
- did=did,
- rates=rates,
- version=g.version
- )
+ is_error, err_msg, ret_status, msg = check_system_stats_installed(
+ g.conn, ret_status)
+ if is_error:
+ return err_msg
def get_data(sid, did, template, check_long_running_query=False):
@@ -484,6 +524,114 @@ def config(sid=None):
return get_data(sid, None, 'config.sql')
+def check_system_stats_installed(conn, ret_status):
+ """
+ Check system_stats extension is present or not
+ :param conn:
+ :param ret_status:
+ :return:
+ """
+ msg = ''
+ status_in, rid_tar = conn.execute_scalar(
+ "SELECT count(*) FROM pg_extension WHERE extname = 'system_stats';"
+ )
+ if not status_in:
+ current_app.logger.debug(
+ "Failed to find the system_stats extension in this database.")
+ return True, internal_server_error(
+ gettext("Failed to find the system_stats extension in "
+ "this database.")
+ ), None, None
+
+ if rid_tar == '0':
+ msg = gettext(
+ "The Extension plugin is not enabled. Please create the "
+ "system_stats extension in this database."
+ )
+ ret_status = False
+ return False, None, ret_status, msg
+
+
[email protected]('/system_stats',
+ endpoint='system_stats')
[email protected]('/system_stats/<int:sid>',
+ endpoint='system_stats_sid')
[email protected]('/system_stats/<int:sid>/<int:did>',
+ endpoint='system_stats_did')
+@login_required
+@check_precondition
+def system_stats(sid=None, did=None):
+
+ resp_data = {}
+
+ if request.args['chart_names'] != '':
+ chart_names = request.args['chart_names'].split(',')
+ if not sid:
+ return internal_server_error(errormsg='Server ID not specified.')
+
+ sql = render_template(
+ "/".join([g.template_path, 'system_stats.sql']), did=did,
+ chart_names=chart_names,
+ )
+ status, res = g.conn.execute_dict(sql)
+ for chart_row in res['rows']:
+ resp_data[chart_row['chart_name']] = \
+ json.loads(chart_row['chart_data'])
+
+ return ajax_response(
+ response=resp_data,
+ status=200
+ )
+
+
[email protected]('/cpu_activity/', endpoint='cpu_activity')
[email protected]('/cpu_activity/<int:sid>',
+ endpoint='get_cpu_activity_by_server_id')
[email protected]('/cpu_activity/<int:sid>/<int:did>',
+ endpoint='get_cpu_activity_by_database_id')
+@login_required
+@check_precondition
+def cpu_activity(sid=None, did=None):
+ """
+ This function returns server activity information
+ :param sid: server id
+ :return:
+ """
+ return get_data(sid, did, 'cpu.sql')
+
+
[email protected]('/os_activity/', endpoint='os_activity')
[email protected]('/os_activity/<int:sid>',
+ endpoint='get_os_activity_by_server_id')
[email protected]('/os_activity/<int:sid>/<int:did>',
+ endpoint='get_os_activity_by_database_id')
+@login_required
+@check_precondition
+def os_activity(sid=None, did=None):
+ """
+ This function returns server lock information
+ :param sid: server id
+ :return:
+ """
+ return get_data(sid, did, 'os.sql')
+
+
[email protected]('/memory_activity/', endpoint='memory_activity')
[email protected]('/memory_activity/<int:sid>',
+ endpoint='get_memory_activity_by_server_id')
[email protected]('/memory_activity/<int:sid>/<int:did>',
+ endpoint='get_memory_activity_by_database_id')
+@login_required
+@check_precondition
+def memory_activity(sid=None, did=None):
+ """
+ This function returns prepared XACT information
+ :param sid: server id
+ :return:
+ """
+ return get_data(sid, did, 'memory.sql')
+
+
@blueprint.route(
'/cancel_query/<int:sid>/<int:pid>', methods=['DELETE']
)
diff --git a/web/pgadmin/dashboard/static/js/Dashboard.jsx b/web/pgadmin/dashboard/static/js/Dashboard.jsx
index 0c70481c1..c8690c3b1 100644
--- a/web/pgadmin/dashboard/static/js/Dashboard.jsx
+++ b/web/pgadmin/dashboard/static/js/Dashboard.jsx
@@ -28,6 +28,7 @@ import _ from 'lodash';
import CachedOutlinedIcon from '@material-ui/icons/CachedOutlined';
import EmptyPanelMessage from '../../../static/js/components/EmptyPanelMessage';
import TabPanel from '../../../static/js/components/TabPanel';
+import Graphs_stats from '../../system_stats/static/js/Graphs_system_stats';
function parseData(data) {
let res = [];
@@ -121,13 +122,19 @@ export default function Dashboard({
sid,
did,
treeNodeInfo,
+ ret_status,
...props
}) {
const classes = useStyles();
let tabs = [gettext('Sessions'), gettext('Locks'), gettext('Prepared Transactions')];
+ let header = [gettext('Database Stats'), gettext('System Stats')];
const [dashData, setdashData] = useState([]);
+ const [sysData, setSysData] = useState([]);
+ const [osData, setOsData] = useState([]);
+ const [memData, setMemData] = useState([]);
const [msg, setMsg] = useState('');
const [tabVal, setTabVal] = useState(0);
+ const [headVal,setHeadVal] = useState(0);
const [refresh, setRefresh] = useState(false);
const [schemaDict, setSchemaDict] = React.useState({});
@@ -139,6 +146,10 @@ export default function Dashboard({
setTabVal(tabVal);
};
+ const headChanged = (e, headVal) => {
+ setHeadVal(headVal);
+ };
+
const serverConfigColumns = [
{
accessor: 'name',
@@ -772,6 +783,124 @@ export default function Dashboard({
}
}, [nodeData, tabVal, did, preferences, refresh, props.dbConnected]);
+ useEffect(() => {
+ let url,
+ message = gettext(
+ 'Please connect to the selected server to view the dashboard.'
+ );
+ if (sid && props.serverConnected) {
+ url = url_for('dashboard.cpu_activity');
+
+ message = gettext('Loading dashboard...');
+ if (did && !props.dbConnected) return;
+ if (did) url += sid + '/' + did;
+ else url += sid;
+
+ const api = getApiInstance();
+ if (node) {
+ api({
+ url: url,
+ type: 'GET',
+ })
+ .then((res) => {
+ setSysData(parseData(res.data));
+ })
+ .catch((error) => {
+ Notify.alert(
+ gettext('Failed to retrieve data from the server.'),
+ _.isUndefined(error.response) ? error.message : error.response.data.errormsg
+ );
+ // show failed message.
+ setMsg(gettext('Failed to retrieve data from the server.'));
+ });
+ } else {
+ setMsg(message);
+ }
+ }
+ if (message != '') {
+ setMsg(message);
+ }
+ }, [nodeData, did, preferences, refresh, props.dbConnected]);
+
+
+ useEffect(() => {
+ let url,
+ message = gettext(
+ 'Please connect to the selected server to view the dashboard.'
+ );
+ if (sid && props.serverConnected) {
+ url = url_for('dashboard.os_activity');
+
+ message = gettext('Loading dashboard...');
+ if (did && !props.dbConnected) return;
+ if (did) url += sid + '/' + did;
+ else url += sid;
+
+ const api = getApiInstance();
+ if (node) {
+ api({
+ url: url,
+ type: 'GET',
+ })
+ .then((res) => {
+ setOsData(parseData(res.data));
+ })
+ .catch((error) => {
+ Notify.alert(
+ gettext('Failed to retrieve data from the server.'),
+ _.isUndefined(error.response) ? error.message : error.response.data.errormsg
+ );
+ // show failed message.
+ setMsg(gettext('Failed to retrieve data from the server.'));
+ });
+ } else {
+ setMsg(message);
+ }
+ }
+ if (message != '') {
+ setMsg(message);
+ }
+ }, [nodeData, did, preferences, refresh, props.dbConnected]);
+
+ useEffect(() => {
+ let url,
+ message = gettext(
+ 'Please connect to the selected server to view the dashboard.'
+ );
+ if (sid && props.serverConnected) {
+ url = url_for('dashboard.memory_activity');
+
+ message = gettext('Loading dashboard...');
+ if (did && !props.dbConnected) return;
+ if (did) url += sid + '/' + did;
+ else url += sid;
+
+ const api = getApiInstance();
+ if (node) {
+ api({
+ url: url,
+ type: 'GET',
+ })
+ .then((res) => {
+ setMemData(parseData(res.data));
+ })
+ .catch((error) => {
+ Notify.alert(
+ gettext('Failed to retrieve data from the server.'),
+ _.isUndefined(error.response) ? error.message : error.response.data.errormsg
+ );
+ // show failed message.
+ setMsg(gettext('Failed to retrieve data from the server.'));
+ });
+ } else {
+ setMsg(message);
+ }
+ }
+ if (message != '') {
+ setMsg(message);
+ }
+ }, [nodeData, did, preferences, refresh, props.dbConnected]);
+
const RefreshButton = () =>{
return(
<PgIconButton
@@ -816,69 +945,108 @@ export default function Dashboard({
{sid && props.serverConnected ? (
<Box className={classes.dashboardPanel}>
<Box className={classes.emptyPanel}>
- {!_.isUndefined(preferences) && preferences.show_graphs && (
- <Graphs
- key={sid + did}
- preferences={preferences}
- sid={sid}
- did={did}
- pageVisible={props.panelVisible}
- ></Graphs>
- )}
- {!_.isUndefined(preferences) && preferences.show_activity && (
- <Box className={classes.panelContent}>
- <Box
- className={classes.cardHeader}
- title={props.dbConnected ? gettext('Database activity') : gettext('Server activity')}
- >
- {props.dbConnected ? gettext('Database activity') : gettext('Server activity')}{' '}
- </Box>
- <Box height="100%" display="flex" flexDirection="column">
- <Box>
- <Tabs
- value={tabVal}
- onChange={tabChanged}
- >
- {tabs.map((tabValue, i) => {
- return <Tab key={i} label={tabValue} />;
- })}
- <RefreshButton/>
- </Tabs>
- </Box>
- <TabPanel value={tabVal} index={0} classNameRoot={classes.tabPanel}>
- <PgTable
- caveTable={false}
- columns={activityColumns}
- data={dashData}
- schema={schemaDict}
- ></PgTable>
- </TabPanel>
- <TabPanel value={tabVal} index={1} classNameRoot={classes.tabPanel}>
- <PgTable
- caveTable={false}
- columns={databaseLocksColumns}
- data={dashData}
- ></PgTable>
- </TabPanel>
- <TabPanel value={tabVal} index={2} classNameRoot={classes.tabPanel}>
- <PgTable
- caveTable={false}
- columns={databasePreparedColumns}
- data={dashData}
- ></PgTable>
- </TabPanel>
- <TabPanel value={tabVal} index={3} classNameRoot={classes.tabPanel}>
- <PgTable
- caveTable={false}
- columns={serverConfigColumns}
- data={dashData}
- ></PgTable>
- </TabPanel>
+
+ <Box className={classes.panelContent}>
+ <Box height="100%" display="flex" flexDirection="column">
+ <Box>
+ {ret_status!==0 && (<Tabs
+ value={headVal}
+ onChange={headChanged}
+ >
+ {header.map((headValue, i) => {
+ return <Tab key={i} label={headValue} />;
+ })}
+ </Tabs>)}
</Box>
+ <TabPanel value={headVal} index={0} classNameRoot={classes.tabPanel}>
+ {!_.isUndefined(preferences) && preferences.show_graphs && (
+ <Graphs
+ key={sid + did}
+ preferences={preferences}
+ sid={sid}
+ did={did}
+ pageVisible={props.panelVisible}
+ ></Graphs>
+ )}
+ {!_.isUndefined(preferences) && preferences.show_activity && (
+ <Box className={classes.panelContent}>
+ <Box
+ className={classes.cardHeader}
+ title={props.dbConnected ? gettext('Database activity') : gettext('Server activity')}
+ >
+ {props.dbConnected ? gettext('Database activity') : gettext('Server activity')}{' '}
+ </Box>
+ <Box height="100%" display="flex" flexDirection="column">
+ <Box>
+ <Tabs
+ value={tabVal}
+ onChange={tabChanged}
+ >
+ {tabs.map((tabValue, i) => {
+ return <Tab key={i} label={tabValue} />;
+ })}
+ <RefreshButton/>
+ </Tabs>
+ </Box>
+ <TabPanel value={tabVal} index={0} classNameRoot={classes.tabPanel}>
+ <PgTable
+ caveTable={false}
+ columns={activityColumns}
+ data={dashData}
+ schema={schemaDict}
+ ></PgTable>
+ </TabPanel>
+ <TabPanel value={tabVal} index={1} classNameRoot={classes.tabPanel}>
+ <PgTable
+ caveTable={false}
+ columns={databaseLocksColumns}
+ data={dashData}
+ ></PgTable>
+ </TabPanel>
+ <TabPanel value={tabVal} index={2} classNameRoot={classes.tabPanel}>
+ <PgTable
+ caveTable={false}
+ columns={databasePreparedColumns}
+ data={dashData}
+ ></PgTable>
+ </TabPanel>
+ <TabPanel value={tabVal} index={3} classNameRoot={classes.tabPanel}>
+ <PgTable
+ caveTable={false}
+ columns={serverConfigColumns}
+ data={dashData}
+ ></PgTable>
+ </TabPanel>
+ </Box>
+ </Box>
+ )}
+ </TabPanel>
+ <TabPanel value={headVal} index={1} classNameRoot={classes.tabPanel}>
+ {!_.isUndefined(preferences) && !_.isUndefined(sysData) && sysData.length>0
+ && !_.isUndefined(osData) && osData.length>0 && !_.isUndefined(memData) &&
+ memData.length>0 && preferences.show_graphs && (
+ <Graphs_stats
+ key={sid + did}
+ preferences={preferences}
+ sid={sid}
+ did={did}
+ pageVisible={props.panelVisible}
+ sysData1 = {sysData}
+ osData1 = {osData}
+ memData1 = {memData}
+ ></Graphs_stats>
+ )}
+ </TabPanel>
</Box>
- )}
+ </Box>
</Box>
</Box>
+ ) : sid && !props.serverConnected ? (
+ <Box className={classes.dashboardPanel}>
+ <div className={classes.emptyPanel}>
+ <EmptyPanelMessage text={gettext(msg)}/>
+ </div>
+ </Box>
) : showDefaultContents() }
</>
);
@@ -898,6 +1066,7 @@ Dashboard.propTypes = {
serverConnected: PropTypes.bool,
dbConnected: PropTypes.bool,
panelVisible: PropTypes.bool,
+ ret_status: PropTypes.bool,
};
export function ChartContainer(props) {
@@ -935,6 +1104,42 @@ ChartContainer.propTypes = {
errorMsg: PropTypes.string,
};
+export function ChartContainersys(props) {
+ return (
+ <div
+ className="card dashboard-sysgraph"
+ role="object-document"
+ tabIndex="0"
+ aria-labelledby={props.id}
+ style={{ borderbottomwidth: '0px' }}
+ >
+ <div className="card-header">
+ <div className="d-flex">
+ <div id={props.id}>{props.title}</div>
+ <div className="ml-auto my-auto legend" ref={props.legendRef}></div>
+ </div>
+ </div>
+ <div className="card-body dashboard-sysgraph-body">
+ <div className={'chart-wrapper ' + (props.errorMsg ? 'd-none' : '')}>
+ {props.children}
+ </div>
+ <ChartError message={props.errorMsg} />
+ </div>
+ </div>
+ );
+}
+
+ChartContainersys.propTypes = {
+ id: PropTypes.string.isRequired,
+ title: PropTypes.string.isRequired,
+ legendRef: PropTypes.oneOfType([
+ PropTypes.func,
+ PropTypes.shape({ current: PropTypes.any }),
+ ]).isRequired,
+ children: PropTypes.node.isRequired,
+ errorMsg: PropTypes.string,
+};
+
export function ChartError(props) {
if (props.message === null) {
return <></>;
diff --git a/web/pgadmin/dashboard/static/scss/_dashboard.scss b/web/pgadmin/dashboard/static/scss/_dashboard.scss
index f28d5cdfc..e52363a6b 100644
--- a/web/pgadmin/dashboard/static/scss/_dashboard.scss
+++ b/web/pgadmin/dashboard/static/scss/_dashboard.scss
@@ -76,6 +76,49 @@
}
}
+
+.dashboard-sysgraph {
+ & .legend {
+ font-size: $tree-font-size;
+ & .legend-value {
+ font-weight: normal;
+ margin-left: 0.25rem;
+ & .legend-label {
+ margin-left: 0.25rem;
+ }
+ }
+ }
+
+ & .dashboard-sysgraph-body {
+ padding: 0.25rem 0.5rem;
+ height: 250px;
+
+ & .flotr-labels {
+ color: $color-fg !important;
+ }
+ & .flotr-legend {
+ border: none !important;
+ padding: 0.25rem 0.5rem;
+ & .flotr-legend-label {
+ color: $color-fg !important;
+ padding-left: 0.25rem;
+ }
+
+ & .flotr-legend-color-box>div {
+ border: none !important;
+ &>div {
+ border: none !important;
+ }
+ }
+
+ & .flotr-legend-bg {
+ border-radius: $border-radius;
+ }
+ }
+ }
+}
+
+
.welcome-logo {
width: 400px;
& .app-name {
diff --git a/web/pgadmin/dashboard/system_stats/static/__init__.py b/web/pgadmin/dashboard/system_stats/static/__init__.py
new file mode 100644
index 000000000..3dd18606b
--- /dev/null
+++ b/web/pgadmin/dashboard/system_stats/static/__init__.py
@@ -0,0 +1,247 @@
+##########################################################################
+#
+# pgAdmin 4 - PostgreSQL Tools
+#
+# Copyright (C) 2013 - 2022, The pgAdmin Development Team
+# This software is released under the PostgreSQL Licence
+#
+##########################################################################
+
+"""A blueprint module implementing the dashboard frame."""
+from flask import render_template, g, request, current_app
+from flask_babel import gettext
+from flask_security import login_required
+import simplejson as json
+from pgadmin.utils import PgAdminModule
+from pgadmin.utils.ajax import make_response as ajax_response,\
+ internal_server_error
+from pgadmin.utils.menu import Panel
+from pgadmin.utils.preferences import Preferences
+from pgadmin.utils.constants import PREF_LABEL_DISPLAY, MIMETYPE_APP_JS, \
+ PREF_LABEL_REFRESH_RATES
+
+from config import PG_DEFAULT_DRIVER
+from ... import __init__, check_precondition, get_data
+
+MODULE_NAME = 'dashboard'
+
+
+class DashboardModule(PgAdminModule):
+ def __init__(self, *args, **kwargs):
+ super(DashboardModule, self).__init__(*args, **kwargs)
+
+ def get_own_menuitems(self):
+ return {}
+
+ def get_own_stylesheets(self):
+ """
+ Returns:
+ list: the stylesheets used by this module.
+ """
+ stylesheets = []
+ return stylesheets
+
+ def get_panels(self):
+ return [
+ Panel(
+ name='dashboard',
+ priority=1,
+ title=gettext('Dashboard'),
+ icon='',
+ content='',
+ is_closeable=True,
+ is_private=False,
+ limit=1,
+ is_iframe=False,
+ can_hide=True
+ ).__dict__
+ ]
+
+ def register_preferences(self):
+ """
+ register_preferences
+ Register preferences for this module.
+ """
+ help_string = gettext('The number of seconds between graph samples.')
+
+ # Register options for Dashboards
+ self.dashboard_preference = Preferences(
+ 'dashboards', gettext('Dashboards')
+ )
+ self.thread_activity_refresh = self.dashboard_preference.register(
+ 'dashboards', 'thread_activity_refresh',
+ gettext("Thread activity refresh rate"), 'integer',
+ 1, min_val=1, max_val=999999,
+ category_label=PREF_LABEL_REFRESH_RATES,
+ help_str=help_string
+ )
+
+ self.cpu_activity_refresh = self.dashboard_preference.register(
+ 'dashboards', 'cpu_activity_refresh',
+ gettext("CPU activity refresh rate"), 'integer',
+ 1, min_val=1, max_val=999999,
+ category_label=PREF_LABEL_REFRESH_RATES,
+ help_str=help_string
+ )
+
+ self.memory_comp_refresh = self.dashboard_preference.register(
+ 'dashboards', 'memory_comp_refresh',
+ gettext("Memory composition refresh rate"), 'integer',
+ 1, min_val=1, max_val=999999,
+ category_label=PREF_LABEL_REFRESH_RATES,
+ help_str=help_string
+ )
+
+ self.disk_info_refresh = self.dashboard_preference.register(
+ 'dashboards', 'disk_info_refresh',
+ gettext("Disk Information refresh rate"), 'integer',
+ 1, min_val=1, max_val=999999,
+ category_label=PREF_LABEL_REFRESH_RATES,
+ help_str=help_string
+ )
+
+ self.avg_load_refresh = self.dashboard_preference.register(
+ 'dashboards', 'avg_load_refresh',
+ gettext("Average load refresh rate"), 'integer',
+ 1, min_val=1, max_val=999999,
+ category_label=PREF_LABEL_REFRESH_RATES,
+ help_str=help_string
+ )
+ self.nics_refresh = self.dashboard_preference.register(
+ 'dashboards', 'nics_refresh',
+ gettext("NICs refresh rate"), 'integer',
+ 1, min_val=1, max_val=999999,
+ category_label=PREF_LABEL_REFRESH_RATES,
+ help_str=help_string
+ )
+
+ def get_exposed_url_endpoints(self):
+ """
+ Returns:
+ list: a list of url endpoints exposed to the client.
+ """
+ return [
+ 'dashboard.cpu_activity',
+ 'dashboard.get_cpu_activity_by_server_id',
+ 'dashboard.get_cpu_activity_by_database_id',
+ 'dashboard.os_activity',
+ 'dashboard.get_os_activity_by_server_id',
+ 'dashboard.get_os_activity_by_database_id',
+ 'dashboard.memory_activity',
+ 'dashboard.get_memory_activity_by_server_id',
+ 'dashboard.get_memory_activity_by_database_id',
+ 'dashboard.system_stats',
+ 'dashboard.system_stats_sid',
+ 'dashboard.system_stats_did',
+ ]
+
+
+blueprint = DashboardModule(MODULE_NAME, __name__)
+
+def check_system_stats_installed(conn, ret_status):
+ """
+ Check system_stats extension is present or not
+ :param conn:
+ :param ret_status:
+ :return:
+ """
+ msg = ''
+ status_in, rid_tar = conn.execute_scalar(
+ "SELECT count(*) FROM pg_extension WHERE extname = 'system_stats';"
+ )
+ if not status_in:
+ current_app.logger.debug(
+ "Failed to find the system_stats extension in this database.")
+ return True, internal_server_error(
+ gettext("Failed to find the system_stats extension in "
+ "this database.")
+ ), None, None
+
+ if rid_tar == '0':
+ msg = gettext(
+ "The Extension plugin is not enabled. Please create the "
+ "system_stats extension in this database."
+ )
+ ret_status = False
+ return False, None, ret_status, msg
+
+
[email protected]('/system_stats',
+ endpoint='system_stats')
[email protected]('/system_stats/<int:sid>',
+ endpoint='system_stats_sid')
[email protected]('/system_stats/<int:sid>/<int:did>',
+ endpoint='system_stats_did')
+@login_required
+@check_precondition
+def system_stats(sid=None, did=None):
+
+ resp_data = {}
+
+ if request.args['chart_names'] != '':
+ chart_names = request.args['chart_names'].split(',')
+
+ if not sid:
+ return internal_server_error(errormsg='Server ID not specified.')
+
+ sql = render_template(
+ "/".join([g.template_path, 'system_stats.sql']), did=did,
+ chart_names=chart_names,
+ )
+ status, res = g.conn.execute_dict(sql)
+ for chart_row in res['rows']:
+ resp_data[chart_row['chart_name']] = \
+ json.loads(chart_row['chart_data'])
+
+ return ajax_response(
+ response=resp_data,
+ status=200
+ )
+
+
[email protected]('/cpu_activity/', endpoint='cpu_activity')
[email protected]('/cpu_activity/<int:sid>',
+ endpoint='get_cpu_activity_by_server_id')
[email protected]('/cpu_activity/<int:sid>/<int:did>',
+ endpoint='get_cpu_activity_by_database_id')
+@login_required
+@check_precondition
+def cpu_activity(sid=None, did=None):
+ """
+ This function returns server activity information
+ :param sid: server id
+ :return:
+ """
+ return get_data(sid, did, 'cpu.sql')
+
+
[email protected]('/os_activity/', endpoint='os_activity')
[email protected]('/os_activity/<int:sid>',
+ endpoint='get_os_activity_by_server_id')
[email protected]('/os_activity/<int:sid>/<int:did>',
+ endpoint='get_os_activity_by_database_id')
+@login_required
+@check_precondition
+def os_activity(sid=None, did=None):
+ """
+ This function returns server lock information
+ :param sid: server id
+ :return:
+ """
+ return get_data(sid, did, 'os.sql')
+
+
[email protected]('/memory_activity/', endpoint='memory_activity')
[email protected]('/memory_activity/<int:sid>',
+ endpoint='get_memory_activity_by_server_id')
[email protected]('/memory_activity/<int:sid>/<int:did>',
+ endpoint='get_memory_activity_by_database_id')
+@login_required
+@check_precondition
+def memory_activity(sid=None, did=None):
+ """
+ This function returns prepared XACT information
+ :param sid: server id
+ :return:
+ """
+ return get_data(sid, did, 'memory.sql')
diff --git a/web/pgadmin/dashboard/system_stats/static/css/Dashboard_system_stats.css b/web/pgadmin/dashboard/system_stats/static/css/Dashboard_system_stats.css
new file mode 100644
index 000000000..cc5f0d54f
--- /dev/null
+++ b/web/pgadmin/dashboard/system_stats/static/css/Dashboard_system_stats.css
@@ -0,0 +1,129 @@
+body {
+ margin: 0;
+ box-sizing: border-box;
+}
+.flex {
+ display: flex;
+ width: 100%;
+ overflow: hidden;
+ height: 100vh;
+}
+.container-chart {
+ min-height: 300px;
+ margin-bottom: 10px;
+ padding: 10px 5px;
+ background: #fff;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ background-clip: border-box;
+ border: 1px solid #dde0e6;
+ border-top: 0px;
+ border-radius: 0.25rem;
+}
+.container-chart-title {
+ width: 95%;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ text-align: left;
+ line-height: 1.5;
+}
+.container-chart-content {
+ width: 95%;
+ gap: 5px;
+ display: flex;
+ align-items: center;
+}
+.container-chart-title h1 {
+ font-weight: 600;
+ line-height: 0;
+}
+.container-chart-title h2 {
+ font-weight: normal;
+ font-size: 1em;
+ width: 80%;
+ word-wrap: break-word;
+ text-align: right;
+}
+.container-chart-content .content-one {
+ display: flex;
+ width: 50%;
+ gap: 0 10px;
+ flex-wrap: wrap;
+ padding: 0.5em;
+}
+.container-chart-content .content-two {
+ width: 50%;
+ height: 100%;
+ padding: 0em;
+ display: flex;
+ list-style-type: none;
+ align-items: center;
+}
+.container-chart-content .content-two li {
+ padding: 2px 0;
+}
+
+.container-chart-content .content-two li span {
+ font-weight: 600;
+ padding-left: 4px;
+ font-size: 1em;
+}
+.container-chart-content h3 {
+ font-weight: normal;
+ font-size: 1em;
+}
+.container-chart-content-span {
+ font-weight: 800;
+ font-size: 1.1em;
+}
+.dashboard-container {
+ position: relative;
+ width: 100%;
+ overflow: auto;
+}
+.dashboard-container-flex {
+ display: flex;
+ width: 100%;
+ flex-wrap: wrap;
+ flex-direction: row;
+ gap: 0;
+ justify-content: center;
+ align-items: center;
+ /* background: #e6e6e6; */
+ padding: 20px 0;
+}
+.dashboard-properties {
+ height: 40px;
+ width: 100%;
+ background: #ff0000;
+ position: fixed;
+}
+.dashboard-box-container {
+ display: flex;
+ /* margin-top: 40px; */
+ padding: 20px;
+ padding-top: 0px;
+ flex-wrap: wrap;
+ /* background: #e6e6e6; */
+ justify-content: space-evenly;
+ gap: 10px;
+}
+.box-container {
+ padding: 10px 20px;
+ width: 284px;
+ background: #fff;
+}
+.box-container h1 {
+ opacity: 0.5;
+ font-weight: normal;
+}
+.box-container h2 {
+ font-weight: 700;
+ word-wrap: break-word;
+}
+.h1 {
+ font-size: 0.8875rem;
+}
\ No newline at end of file
diff --git a/web/pgadmin/dashboard/system_stats/static/js/Dashboard_system_stats.jsx b/web/pgadmin/dashboard/system_stats/static/js/Dashboard_system_stats.jsx
new file mode 100644
index 000000000..ba3bdc690
--- /dev/null
+++ b/web/pgadmin/dashboard/system_stats/static/js/Dashboard_system_stats.jsx
@@ -0,0 +1,132 @@
+// // /////////////////////////////////////////////////////////////
+// // //
+// // // pgAdmin 4 - PostgreSQL Tools
+// // //
+// // // Copyright (C) 2013 - 2022, The pgAdmin Development Team
+// // // This software is released under the PostgreSQL Licence
+// // //
+// // /////////////////////////////////////////////////////////////
+import React from 'react';
+import PropTypes from 'prop-types';
+
+
+export default function CPUContainer({_sysData}) {
+ return (
+ <div className="container-chart">
+ <div className="container-chart-title">
+ <h2><span style={{fontWeight: 'bold'}}>{_sysData ? _sysData[0]?.model_name :'Loading'}</span></h2>
+ <h2>{_sysData ? _sysData[0]?.description :'Loading'}</h2>
+ </div>
+ <div className="container-chart-content">
+ <div className="content-one">
+ <h3>
+ Vendor<br />
+ <span className="container-chart-content-span">
+ {_sysData ? _sysData[0]?.vendor :'Loading'}
+ </span>
+ </h3>
+ <h3>
+ Clock Speed<br />
+ <span className="container-chart-content-span">
+ {_sysData ? _sysData[0]?.clock_speed_hz :'Loading'} GHz
+ </span>
+ </h3>
+ </div>
+ <div className="content-two">
+ <div>
+ <li>Cores:<span>{_sysData ? _sysData[0]?.no_of_cores :'Loading'}</span></li>
+ <li>Logical processors:<span>{_sysData ? _sysData[0]?.logical_processor :'Loading'}</span></li>
+ <li>Physical processors:<span>{_sysData ? _sysData[0]?.physical_processor :'Loading'}</span></li>
+ <li>L1_D cache:<span> {_sysData ? _sysData[0]?.l1dcache_size :'Loading'}</span></li>
+ <li>L1 cache:<span>{_sysData ? _sysData[0]?.l1icache_size :'Loading'}</span></li>
+ <li>L2 cache:<span>{_sysData ? _sysData[0]?.l2cache_size :'Loading'}</span></li>
+ <li>L3 cache:<span>{_sysData ? _sysData[0]?.l3cache_size :'Loading'}</span></li>
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+}
+
+CPUContainer.propTypes = {
+ _sysData: PropTypes.array,
+};
+
+
+
+export function OSContainer({_sysData}) {
+ return (
+ <div className="container-chart">
+ <div className="container-chart-title">
+ <h2><span style={{fontWeight: 'bold',fontsize: '1.2rem'}}>{_sysData ? _sysData[0]?.name :'Loading'}</span></h2>
+ <h2>{_sysData ? _sysData[0]?.version :'Loading'}</h2>
+ </div>
+ <div className="container-chart-content">
+ <div className="content-one">
+ <h3>
+ Host Name<br />
+ <span className="container-chart-content-span">
+ {_sysData ? _sysData[0]?.host_name :'Loading'}
+ </span>
+ </h3>
+ <h3>
+ Os up since<br />
+ <span className="container-chart-content-span">
+ {_sysData ? _sysData[0]?.os_up_since_seconds :'Loading'} Sec
+ </span>
+ </h3>
+ </div>
+ <div className="content-two">
+ <div>
+ <li>Architecture<span>{_sysData ? _sysData[0]?.architecture :'Loading'}</span></li>
+ <li>Handle Count:<span>{_sysData ? _sysData[0]?.handle_count :'Loading'}</span></li>
+ <li>Process Count:<span>{_sysData ? _sysData[0]?.process_count :'Loading'}</span></li>
+ <li>Thread Count:<span>{_sysData ? _sysData[0]?.thread_count :'Loading'}</span></li>
+ <li>Process Count:<span>{_sysData ? _sysData[0]?.process_count :'Loading'}</span></li>
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+}
+
+OSContainer.propTypes = {
+ _sysData: PropTypes.array,
+};
+
+export function MEMContainer({_sysData}) {
+ return (
+ <div className="container-chart">
+ <div className="container-chart-title">
+ </div>
+ <div className="container-chart-content">
+ <div className="content-one">
+ <h3>
+ Total Memory<br />
+ <span className="container-chart-content-span">
+ {_sysData ? _sysData[0]?.total_memory :'Loading'}
+ </span>
+ </h3>
+ <h3>
+ Used Memory
+ <br />
+ <span className="container-chart-content-span">
+ {_sysData ? _sysData[0]?.used_memory :'Loading'} GHz
+ </span>
+ </h3>
+ </div>
+ <div className="content-two">
+ <div>
+ <li>Total Swap Memory:<span>{_sysData ? _sysData[0]?.swap_total :'Loading'}</span></li>
+ <li>Used Swap Memory:<span>{_sysData ? _sysData[0]?.swap_used :'Loading'}</span></li>
+ <li>Free Swap Memory:<span>{_sysData ? _sysData[0]?.swap_free :'Loading'}</span></li>
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+}
+
+MEMContainer.propTypes = {
+ _sysData: PropTypes.array,
+};
\ No newline at end of file
diff --git a/web/pgadmin/dashboard/system_stats/static/js/Graphs_system_stats.jsx b/web/pgadmin/dashboard/system_stats/static/js/Graphs_system_stats.jsx
new file mode 100644
index 000000000..9b7036c17
--- /dev/null
+++ b/web/pgadmin/dashboard/system_stats/static/js/Graphs_system_stats.jsx
@@ -0,0 +1,410 @@
+/////////////////////////////////////////////////////////////
+//
+// pgAdmin 4 - PostgreSQL Tools
+//
+// Copyright (C) 2013 - 2022, The pgAdmin Development Team
+// This software is released under the PostgreSQL Licence
+//
+//////////////////////////////////////////////////////////////
+import React, { useEffect, useRef, useState, useReducer, useCallback, useMemo } from 'react';
+import {LineChart, DATA_POINT_STYLE, DATA_POINT_SIZE} from 'sources/chartjs';
+import { DashboardRowCol, DashboardRow, ChartContainersys} from '../../../static/js/Dashboard';
+import url_for from 'sources/url_for';
+import axios from 'axios';
+import gettext from 'sources/gettext';
+import {getGCD, getEpoch} from 'sources/utils';
+import {useInterval, usePrevious} from 'sources/custom_hooks';
+import PropTypes from 'prop-types';
+import { PieChart } from '../../../../static/js/chartjs';
+import CPUContainer,{ MEMContainer, OSContainer } from './Dashboard_system_stats';
+import _ from 'lodash';
+
+
+export const X_AXIS_LENGTH = 75;
+
+/* Transform the labels data to suit ChartJS */
+export function transformData(labels, refreshRate, use_diff_point_style) {
+ const colors = ['#00BCD4', '#9CCC65', '#E64A19','#AFB4FF','#781C68'];
+ let datasets = Object.keys(labels).map((label, i)=>{
+ return {
+ label: label,
+ data: labels[label] || [],
+ borderColor: colors[i],
+ backgroundColor: colors[i],
+ pointHitRadius: DATA_POINT_SIZE,
+ pointStyle: use_diff_point_style ? DATA_POINT_STYLE[i] : 'circle'
+ };
+ }) || [];
+
+ return {
+ labels: [...Array(X_AXIS_LENGTH).keys()],
+ datasets: datasets,
+ refreshRate: refreshRate,
+ };
+}
+
+/* Custom ChartJS legend callback */
+export function generateLegend(chart) {
+ var text = [];
+ text.push('<div class="' + chart.id + '-legend d-flex">');
+ for (let chart_val of chart.data.datasets) {
+ text.push('<div class="legend-value"><span style="background-color:' + chart_val.backgroundColor + '"> </span>');
+ if (chart_val.label) {
+ text.push('<span class="legend-label">' + chart_val.label + '</span>');
+ }
+ text.push('</div>');
+ }
+ text.push('</div>');
+ return text.join('');
+}
+
+/* URL for fetching graphs data */
+export function getStatsUrl(sid=-1, did=-1, chart_names=[]) {
+ let base_url = url_for('dashboard.system_stats');
+ base_url += '/' + sid;
+ base_url += (did > 0) ? ('/' + did) : '';
+ base_url += '?chart_names=' + chart_names.join(',');
+ return base_url;
+}
+
+/* This will process incoming charts data add it the previous charts
+ * data to get the new state.
+ */
+export function statsReducer(state, action) {
+
+ if(action.reset) {
+ return action.reset;
+ }
+
+ if(!action.incoming) {
+ return state;
+ }
+
+ if(!action.counterData) {
+ action.counterData = action.incoming;
+ }
+
+ let newState = {};
+ Object.keys(action.incoming).forEach(label => {
+ if(state[label]) {
+ newState[label] = [
+ action.counter ? action.incoming[label] - action.counterData[label] : action.incoming[label],
+ ...state[label].slice(0, X_AXIS_LENGTH-1),
+ ];
+ } else {
+ newState[label] = [
+ action.counter ? action.incoming[label] - action.counterData[label] : action.incoming[label],
+ ];
+ }
+ });
+ return newState;
+}
+
+const chartsDefault = {
+ 'thread_activity': {'Process count': [], 'Handle count': [], 'Thread count': []},
+ 'cpu_activity': {'Usermode Process': [], 'Kernelmode Process': []},
+ 'memory_comp': {'Used Memory': [], 'Free Memory': []},
+ 'disk_info': {'File System':[], 'Total Space': [], 'Used Space': []},
+ 'avg_load': {'Per 1 Min': [], 'Per 5 Min': [], 'Per 10 Min': []},
+ 'nics': {'Tx Bytes': [], 'Tx Packets': [], 'Rx Bytes': [], 'Rx Packets':[]},
+};
+
+export default function Graphs_stats({preferences, sid, did, pageVisible, enablePoll=true, sysData1, osData1, memData1}) {
+
+ const refreshOn = useRef(null);
+ const prevPrefernces = usePrevious(preferences);
+
+ const [threadactivity, threadstatsReduce] = useReducer(statsReducer, chartsDefault['thread_activity']);
+ const [cpuactivity, cpuStatsReduce] = useReducer(statsReducer, chartsDefault['cpu_activity']);
+ const [memorycomp, memStatsReduce] = useReducer(statsReducer, chartsDefault['memory_comp']);
+ const [diskinfo, diskStatsReduce] = useReducer(statsReducer, chartsDefault['disk_info']);
+ const [avgload, loadStatsReduce] = useReducer(statsReducer, chartsDefault['avg_load']);
+ const [nic, nicStatsReduce] = useReducer(statsReducer, chartsDefault['nics']);
+
+
+ const [counterData, setCounterData] = useState({});
+
+ const [errorMsg, setErrorMsg] = useState(null);
+ const [pollDelay, setPollDelay] = useState(1000);
+ const [chartDrawnOnce, setChartDrawnOnce] = useState(false);
+
+ useEffect(()=>{
+ let calcPollDelay = false;
+ if(prevPrefernces) {
+ if(prevPrefernces['thread_activity_refresh'] != preferences['thread_activity_refresh']) {
+ threadstatsReduce({reset: chartsDefault['thread_activity']});
+ calcPollDelay = true;
+ }
+ if(prevPrefernces['cpu_activity_refresh'] != preferences['']) {
+ cpuStatsReduce({reset:chartsDefault['cpu_activity']});
+ calcPollDelay = true;
+ }
+ if(prevPrefernces['memory_comp_refresh'] != preferences['memory_comp_refresh']) {
+ memStatsReduce({reset:chartsDefault['memory_comp']});
+ calcPollDelay = true;
+ }
+ if(prevPrefernces['disk_info_refresh'] != preferences['disk_info_refresh']) {
+ diskStatsReduce({reset:chartsDefault['disk_info']});
+ calcPollDelay = true;
+ }
+ if(prevPrefernces['avg_load_refresh'] != preferences['avg_load_refresh']) {
+ loadStatsReduce({reset:chartsDefault['avg_load']});
+ calcPollDelay = true;
+ }
+ if(prevPrefernces['nics_refresh'] != preferences['nics_refresh']) {
+ nicStatsReduce({reset:chartsDefault['nics']});
+ calcPollDelay = true;
+ }
+ } else {
+ calcPollDelay = true;
+ }
+ if(calcPollDelay) {
+ setPollDelay(
+ getGCD(Object.keys(chartsDefault).map((name)=>preferences[name+'_refresh']))*1000
+ );
+ }
+ }, [preferences]);
+
+ useEffect(()=>{
+ /* Charts rendered are not visible when, the dashboard is hidden but later visible */
+ if(pageVisible && !chartDrawnOnce) {
+ setChartDrawnOnce(true);
+ }
+ }, [pageVisible]);
+
+ useInterval(()=>{
+ const currEpoch = getEpoch();
+ if(refreshOn.current === null) {
+ let tmpRef = {};
+ Object.keys(chartsDefault).forEach((name)=>{
+ tmpRef[name] = currEpoch;
+ });
+ refreshOn.current = tmpRef;
+ }
+
+ let getFor = [];
+ Object.keys(chartsDefault).forEach((name)=>{
+ if(currEpoch >= refreshOn.current[name]) {
+ getFor.push(name);
+ refreshOn.current[name] = currEpoch + preferences[name+'_refresh'];
+ }
+ });
+
+ let path = getStatsUrl(sid, did, getFor);
+ if (!pageVisible){
+ return;
+ }
+ axios.get(path)
+ .then((resp)=>{
+ let data = resp.data;
+ setErrorMsg(null);
+ threadstatsReduce({incoming: data['thread_activity']});
+ cpuStatsReduce({incoming: data['cpu_activity'], counter: true, counterData: counterData['cpu_activity']});
+ memStatsReduce({incoming: data['memory_comp'], counter: true, counterData: counterData['memory_comp']});
+ diskStatsReduce({incoming: data['disk_info'], counter: true, counterData: counterData['disk_info']});
+ loadStatsReduce({incoming: data['avg_load'], counter: true, counterData: counterData['avg_load']});
+ nicStatsReduce({incoming: data['nics'], counter: true, counterData: counterData['nics']});
+
+
+ setCounterData((prevCounterData)=>{
+ return {
+ ...prevCounterData,
+ ...data,
+ };
+ });
+ })
+ .catch((error)=>{
+ if(!errorMsg) {
+ threadstatsReduce({reset: chartsDefault['thread_activity']});
+ cpuStatsReduce({reset:chartsDefault['cpu_activity']});
+ memStatsReduce({reset:chartsDefault['memory_comp']});
+ diskStatsReduce({reset:chartsDefault['disk_info']});
+ loadStatsReduce({reset:chartsDefault['avg_load']});
+ nicStatsReduce({reset:chartsDefault['nics']});
+ setCounterData({});
+ if(error.response) {
+ if (error.response.status === 428) {
+ setErrorMsg(gettext('Please connect to the selected server to view the graph.'));
+ } else {
+ setErrorMsg(gettext('An error occurred whilst rendering the graph.'));
+ }
+ } else if(error.request) {
+ setErrorMsg(gettext('Not connected to the server or the connection to the server has been closed.'));
+ return;
+ } else {
+ console.error(error);
+ }
+ }
+ });
+ }, enablePoll ? pollDelay : -1);
+
+ return (
+ <>
+ <div data-testid='graph-poll-delay' className='d-none'>{pollDelay}</div>
+ {chartDrawnOnce &&
+ <GraphsWrapper
+ threadStats={transformData(threadactivity, preferences['thread_activity_refresh'], preferences['use_diff_point_style'])}
+ cpuStats={transformData(cpuactivity, preferences['cpu_activity_refresh'], preferences['use_diff_point_style'])}
+ memStats={transformData(memorycomp, preferences['memory_comp_refresh'], preferences['use_diff_point_style'])}
+ diskStats={transformData(diskinfo, preferences['disk_info_refresh'], preferences['use_diff_point_style'])}
+ avgStats={transformData(avgload, preferences['avg_load_refresh'], preferences['use_diff_point_style'])}
+ nicStats={transformData(nic, preferences['nics_refresh'], preferences['use_diff_point_style'])}
+ errorMsg={errorMsg}
+ showTooltip={preferences['graph_mouse_track']}
+ showDataPoints={preferences['graph_data_points']}
+ lineBorderWidth={preferences['graph_line_border_width']}
+ isDatabase={did > 0}
+ sysData2={sysData1}
+ osData2={osData1}
+ memData2={memData1}
+ />
+ }
+ </>
+ );
+}
+
+Graphs_stats.propTypes = {
+ preferences: PropTypes.object.isRequired,
+ sid: PropTypes.oneOfType([
+ PropTypes.string.isRequired,
+ PropTypes.number.isRequired,
+ ]),
+ did: PropTypes.oneOfType([
+ PropTypes.string.isRequired,
+ PropTypes.number.isRequired,
+ ]),
+ pageVisible: PropTypes.bool,
+ enablePoll: PropTypes.bool,
+ sysData1: PropTypes.array,
+ osData1: PropTypes.array,
+ memData1: PropTypes.array,
+};
+
+export function GraphsWrapper(props) {
+ const threadStatsLegendRef = useRef();
+ const cpuStatsLegendRef = useRef();
+ const memStatsLegendRef = useRef();
+ const diskStatsLegendRef = useRef();
+ const avgStatsLegendRef = useRef();
+ const nicStatsLegendRef = useRef();
+
+ const options = useMemo(()=>({
+ elements: {
+ point: {
+ radius: props.showDataPoints ? DATA_POINT_SIZE : 0,
+ },
+ line: {
+ borderWidth: props.lineBorderWidth,
+ },
+ },
+ plugins: {
+ legend: {
+ display: false,
+ },
+ tooltip: {
+ enabled: props.showTooltip,
+ callbacks: {
+ title: function(tooltipItem) {
+ let title = '';
+ try {
+ title = parseInt(tooltipItem[0].label) * tooltipItem[0].chart?.data.refreshRate + gettext(' seconds ago');
+ } catch (error) {
+ title = '';
+ }
+ return title;
+ },
+ },
+ }
+ },
+ scales: {
+ x: {
+ reverse: true,
+ },
+ y: {
+ min: 0,
+ }
+ },
+ }), [props.showTooltip, props.showDataPoints, props.lineBorderWidth]);
+ const updateOptions = useMemo(()=>({duration: 0}), []);
+
+ const onInitCallback = useCallback(
+ (legendRef)=>(chart)=>{
+ legendRef.current.innerHTML = generateLegend(chart);
+ }
+ );
+
+ return (
+ <>
+ <DashboardRow>
+ <DashboardRowCol breakpoint='md' parts={4}>
+ <ChartContainersys id='thread-graph' title={gettext('Thread Count')} legendRef={threadStatsLegendRef} errorMsg={props.errorMsg}>
+ <LineChart options={options} data={props.threadStats} updateOptions={updateOptions}
+ onInit={onInitCallback(threadStatsLegendRef)}/>
+ </ChartContainersys>
+ {!_.isUndefined(props.osData2) && props.osData2.length>0 && (<OSContainer _sysData={props.osData2}></OSContainer>)}
+ </DashboardRowCol>
+ <DashboardRowCol breakpoint='md' parts={4}>
+ <ChartContainersys id='cpu-graph' title={gettext('CPU Activity')} legendRef={cpuStatsLegendRef} errorMsg={props.errorMsg}>
+ <LineChart options={options} data={props.cpuStats} updateOptions={updateOptions}
+ onInit={onInitCallback(cpuStatsLegendRef)}/>
+ </ChartContainersys>
+ {!_.isUndefined(props.sysData2) && props.sysData2.length>0 && (<CPUContainer _sysData={props.sysData2}></CPUContainer>)}
+ </DashboardRowCol>
+ <DashboardRowCol breakpoint='md' parts={4}>
+ <ChartContainersys id='mem-graph' title={gettext('Memory')} legendRef={memStatsLegendRef} errorMsg={props.errorMsg}>
+ <LineChart options={options} data={props.memStats} updateOptions={updateOptions}
+ onInit={onInitCallback(memStatsLegendRef)}/>
+ </ChartContainersys>
+ {!_.isUndefined(props.memData2) && props.memData2.length>0 && (<MEMContainer _sysData={props.memData2}></MEMContainer>)}
+ </DashboardRowCol>
+ </DashboardRow>
+ <DashboardRow>
+ <DashboardRowCol breakpoint='md' parts={4}>
+ <ChartContainersys id='disk-graph' title={gettext('Disk')} legendRef={diskStatsLegendRef} errorMsg={props.errorMsg}>
+ <PieChart options={options} data={props.diskStats} updateOptions={updateOptions}
+ onInit={onInitCallback(diskStatsLegendRef)}/>
+ </ChartContainersys>
+ {/* {!_.isUndefined(props.sysData2) && props.sysData2.length>0 && (<CPUContainer _sysData={props.sysData2}></CPUContainer>)} */}
+ </DashboardRowCol>
+ <DashboardRowCol breakpoint='md' parts={4}>
+ <ChartContainersys id='avg-graph' title={gettext('Average Load')} legendRef={avgStatsLegendRef} errorMsg={props.errorMsg}>
+ <LineChart options={options} data={props.avgStats} updateOptions={updateOptions}
+ onInit={onInitCallback(avgStatsLegendRef)}/>
+ </ChartContainersys>
+ {/* {!_.isUndefined(props.sysData2) && props.sysData2.length>0 && (<CPUContainer _sysData={props.sysData2}></CPUContainer>)} */}
+ </DashboardRowCol>
+ <DashboardRowCol breakpoint='md' parts={4}>
+ <ChartContainersys id='nic-graph' title={gettext('NIC Detail')} legendRef={nicStatsLegendRef} errorMsg={props.errorMsg}>
+ <LineChart options={options} data={props.nicStats} updateOptions={updateOptions}
+ onInit={onInitCallback(nicStatsLegendRef)}/>
+ </ChartContainersys>
+ {/* {!_.isUndefined(props.sysData2) && props.sysData2.length>0 && (<CPUContainer _sysData={props.sysData2}></CPUContainer>)} */}
+ </DashboardRowCol>
+ </DashboardRow>
+ </>
+ );
+}
+
+const propTypeStats = PropTypes.shape({
+ labels: PropTypes.array.isRequired,
+ datasets: PropTypes.array,
+ refreshRate: PropTypes.number.isRequired,
+});
+GraphsWrapper.propTypes = {
+ threadStats: propTypeStats.isRequired,
+ cpuStats: propTypeStats.isRequired,
+ memStats: propTypeStats.isRequired,
+ diskStats: propTypeStats.isRequired,
+ avgStats: propTypeStats.isRequired,
+ nicStats: propTypeStats.isRequired,
+ errorMsg: PropTypes.string,
+ showTooltip: PropTypes.bool.isRequired,
+ showDataPoints: PropTypes.bool.isRequired,
+ lineBorderWidth: PropTypes.number.isRequired,
+ isDatabase: PropTypes.bool.isRequired,
+ sysData2: PropTypes.array.isRequired,
+ osData2: PropTypes.array.isRequired,
+ memData2: PropTypes.array.isRequired,
+
+
+};
diff --git a/web/pgadmin/dashboard/templates/dashboard/sql/default/cpu.sql b/web/pgadmin/dashboard/templates/dashboard/sql/default/cpu.sql
new file mode 100644
index 000000000..709f4ce9e
--- /dev/null
+++ b/web/pgadmin/dashboard/templates/dashboard/sql/default/cpu.sql
@@ -0,0 +1 @@
+/*pga4dash*/ SELECT model_name, vendor,description, logical_processor, physical_processor, no_of_cores, clock_speed_hz, l1dcache_size, l1icache_size, l2cache_size, l3cache_size FROM pg_sys_cpu_info();
diff --git a/web/pgadmin/dashboard/templates/dashboard/sql/default/memory.sql b/web/pgadmin/dashboard/templates/dashboard/sql/default/memory.sql
new file mode 100644
index 000000000..8cab1d632
--- /dev/null
+++ b/web/pgadmin/dashboard/templates/dashboard/sql/default/memory.sql
@@ -0,0 +1 @@
+/*pga4dash*/ SELECT total_memory, used_memory, free_memory,swap_total, swap_used, swap_free FROM pg_sys_memory_info();
diff --git a/web/pgadmin/dashboard/templates/dashboard/sql/default/os.sql b/web/pgadmin/dashboard/templates/dashboard/sql/default/os.sql
new file mode 100644
index 000000000..97c011c92
--- /dev/null
+++ b/web/pgadmin/dashboard/templates/dashboard/sql/default/os.sql
@@ -0,0 +1 @@
+/*pga4dash*/ SELECT version, name, handle_count, process_count,host_name, architecture, os_up_since_seconds, last_bootup_time, thread_count FROM pg_sys_os_info();
diff --git a/web/pgadmin/dashboard/templates/dashboard/sql/default/system_stats.sql b/web/pgadmin/dashboard/templates/dashboard/sql/default/system_stats.sql
new file mode 100644
index 000000000..5907f16dc
--- /dev/null
+++ b/web/pgadmin/dashboard/templates/dashboard/sql/default/system_stats.sql
@@ -0,0 +1,41 @@
+/*pga4dash*/
+{% set add_union = false %}
+{% if 'thread_activity' in chart_names %}
+{% set add_union = true %}
+SELECT 'thread_activity' as chart_name, row_to_json(row(process_count, handle_count, thread_count)) as chart_data FROM pg_sys_os_info()
+{% endif %}
+{% if add_union and 'cpu_activity' in chart_names %}
+UNION ALL
+{% endif %}
+{% if 'cpu_activity' in chart_names %}
+{% set add_union = true %}
+SELECT 'cpu_activity' as chart_name, row_to_json(row(sum(usermode_normal_process_percent),sum(kernelmode_process_percent))) as chart_data FROM pg_sys_cpu_usage_info()
+{% endif %}
+{% if add_union and 'memory_comp' in chart_names %}
+UNION ALL
+{% endif %}
+{% if 'memory_comp' in chart_names %}
+{% set add_union = true %}
+SELECT 'memory_comp' as chart_name, row_to_json(row(used_memory,free_memory)) as chart_data FROM pg_sys_memory_info()
+{% endif %}
+{% if add_union and 'disk_info' in chart_names %}
+UNION ALL
+{% endif %}
+{% if 'disk_info' in chart_names %}
+{% set add_union = true %}
+SELECT 'disk_info' as chart_name, row_to_json(row(file_system,total_space,used_space)) as chart_data FROM pg_sys_disk_info()
+{% endif %}
+{% if add_union and 'avg_load' in chart_names %}
+UNION ALL
+{% endif %}
+{% if 'avg_load' in chart_names %}
+{% set add_union = true %}
+SELECT 'avg_load' as chart_name, row_to_json(row(load_avg_one_minute,load_avg_five_minutes,load_avg_ten_minutes)) as chart_data FROM pg_sys_load_avg_info()
+{% endif %}
+{% if add_union and 'nics' in chart_names %}
+UNION ALL
+{% endif %}
+{% if 'nics' in chart_names %}
+{% set add_union = true %}
+SELECT 'nics' as chart_name, row_to_json(row(interface_name,tx_bytes,tx_packets,rx_bytes,rx_packets)) as chart_data FROM pg_sys_network_info();
+{% endif %}