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 + + +@blueprint.route('/system_stats', + endpoint='system_stats') +@blueprint.route('/system_stats/<int:sid>', + endpoint='system_stats_sid') +@blueprint.route('/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 + ) + + +@blueprint.route('/cpu_activity/', endpoint='cpu_activity') +@blueprint.route('/cpu_activity/<int:sid>', + endpoint='get_cpu_activity_by_server_id') +@blueprint.route('/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') + + +@blueprint.route('/os_activity/', endpoint='os_activity') +@blueprint.route('/os_activity/<int:sid>', + endpoint='get_os_activity_by_server_id') +@blueprint.route('/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') + + +@blueprint.route('/memory_activity/', endpoint='memory_activity') +@blueprint.route('/memory_activity/<int:sid>', + endpoint='get_memory_activity_by_server_id') +@blueprint.route('/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 + + +@blueprint.route('/system_stats', + endpoint='system_stats') +@blueprint.route('/system_stats/<int:sid>', + endpoint='system_stats_sid') +@blueprint.route('/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 + ) + + +@blueprint.route('/cpu_activity/', endpoint='cpu_activity') +@blueprint.route('/cpu_activity/<int:sid>', + endpoint='get_cpu_activity_by_server_id') +@blueprint.route('/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') + + +@blueprint.route('/os_activity/', endpoint='os_activity') +@blueprint.route('/os_activity/<int:sid>', + endpoint='get_os_activity_by_server_id') +@blueprint.route('/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') + + +@blueprint.route('/memory_activity/', endpoint='memory_activity') +@blueprint.route('/memory_activity/<int:sid>', + endpoint='get_memory_activity_by_server_id') +@blueprint.route('/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 %}