From 12c7e0a43492e8fee9e09d8168113e8105e234ca Mon Sep 17 00:00:00 2001 From: James Chapman Date: Thu, 13 Feb 2025 20:31:08 +0000 Subject: [PATCH] Issue 6429 - UI - clicking on a database suffix under the Monitor tab crashes UI Bug description: Clicking on a db suffix under the MOnitor tab causes the UI to crash when the instance is configured with the mdb db engine. Fix description: Introduced separate database and suffix monitor classes tailored for mdb. Parent class detects the configured db engine and calls the appropriate monitor class. Fixes: https://github.com/389ds/389-ds-base/issues/6429 Reviewed by: --- .../src/lib/database/databaseConfig.jsx | 2 - .../389-console/src/lib/monitor/dbMonitor.jsx | 385 +++++++++++++++++- .../src/lib/monitor/suffixMonitor.jsx | 363 ++++++++++++++++- src/cockpit/389-console/src/monitor.jsx | 90 +++- 4 files changed, 816 insertions(+), 24 deletions(-) diff --git a/src/cockpit/389-console/src/lib/database/databaseConfig.jsx b/src/cockpit/389-console/src/lib/database/databaseConfig.jsx index d78448bfac..82afa35c52 100644 --- a/src/cockpit/389-console/src/lib/database/databaseConfig.jsx +++ b/src/cockpit/389-console/src/lib/database/databaseConfig.jsx @@ -1145,12 +1145,10 @@ export class GlobalDatabaseConfigMDB extends React.Component { // Check if a setting was changed, if so enable the save button for (const config_attr of check_attrs) { if (this.state[config_attr] !== this.state['_' + config_attr]) { - // jc console.log(config_attr); saveBtnDisabled = false; break; } } - // jc console.log(saveBtnDisabled); this.setState({ saveBtnDisabled }); diff --git a/src/cockpit/389-console/src/lib/monitor/dbMonitor.jsx b/src/cockpit/389-console/src/lib/monitor/dbMonitor.jsx index f3f51733b8..08aa1aaea1 100644 --- a/src/cockpit/389-console/src/lib/monitor/dbMonitor.jsx +++ b/src/cockpit/389-console/src/lib/monitor/dbMonitor.jsx @@ -326,7 +326,6 @@ export class DatabaseMonitor extends React.Component { - {_("Normalized DN Cache")}}>
@@ -511,12 +510,394 @@ export class DatabaseMonitor extends React.Component { // Prop types and defaults DatabaseMonitor.propTypes = { + data: PropTypes.object, serverId: PropTypes.string, enableTree: PropTypes.func, }; DatabaseMonitor.defaultProps = { + data: {}, serverId: "", }; -export default DatabaseMonitor; +export class DatabaseMonitorMDB extends React.Component { + constructor (props) { + super(props); + this.state = { + activeTabKey: 0, + data: {}, + loading: true, + // refresh chart + cache_refresh: "", + count: 10, + ndnCount: 5, + dbCacheList: [], + ndnCacheList: [], + ndnCacheUtilList: [] + }; + + // Toggle currently active tab + this.handleNavSelect = (event, tabIndex) => { + this.setState({ + activeTabKey: tabIndex + }); + }; + + this.startCacheRefresh = this.startCacheRefresh.bind(this); + this.refreshCache = this.refreshCache.bind(this); + } + + componentDidMount() { + this.resetChartData(); + this.refreshCache(); + this.startCacheRefresh(); + this.props.enableTree(); + } + + componentWillUnmount() { + this.stopCacheRefresh(); + } + + resetChartData() { + this.setState({ + data: { + normalizeddncachehitratio: [0], + maxnormalizeddncachesize: [0], + currentnormalizeddncachesize: [0], + normalizeddncachetries: [0], + normalizeddncachehits: [0], + normalizeddncacheevictions: [0], + currentnormalizeddncachecount: [0], + normalizeddncachethreadsize: [0], + normalizeddncachethreadslots: [0], + }, + ndnCacheList: [ + { name: "", x: "1", y: 0 }, + { name: "", x: "2", y: 0 }, + { name: "", x: "3", y: 0 }, + { name: "", x: "4", y: 0 }, + { name: "", x: "5", y: 0 }, + ], + ndnCacheUtilList: [ + { name: "", x: "1", y: 0 }, + { name: "", x: "2", y: 0 }, + { name: "", x: "3", y: 0 }, + { name: "", x: "4", y: 0 }, + { name: "", x: "5", y: 0 }, + ], + }); + } + + refreshCache() { + // Search for db cache stat and update state + const cmd = [ + "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket", + "monitor", "ldbm" + ]; + cockpit + .spawn(cmd, { superuser: true, err: "message" }) + .done(content => { + const config = JSON.parse(content); + let count = this.state.count + 1; + const ndnCount = this.state.ndnCount + 1; + if (count > 100) { + // Keep progress count in check + count = 1; + } + + // Build up the DB Cache chart data + const dbratio = config.attrs.dbcachehitratio[0]; + const chart_data = this.state.dbCacheList; + chart_data.shift(); + chart_data.push({ name: _("Cache Hit Ratio"), x: count.toString(), y: parseInt(dbratio) }); + + // Build up the NDN Cache chart data + const ndnratio = config.attrs.normalizeddncachehitratio[0]; + const ndn_chart_data = this.state.ndnCacheList; + ndn_chart_data.shift(); + ndn_chart_data.push({ name: _("Cache Hit Ratio"), x: count.toString(), y: parseInt(ndnratio) }); + + // Build up the DB Cache Util chart data + const ndn_util_chart_data = this.state.ndnCacheUtilList; + const currNDNSize = parseInt(config.attrs.currentnormalizeddncachesize[0]); + const maxNDNSize = parseInt(config.attrs.maxnormalizeddncachesize[0]); + const ndn_utilization = (currNDNSize / maxNDNSize) * 100; + ndn_util_chart_data.shift(); + ndn_util_chart_data.push({ name: _("Cache Utilization"), x: ndnCount.toString(), y: parseInt(ndn_utilization) }); + + this.setState({ + data: config.attrs, + loading: false, + dbCacheList: chart_data, + ndnCacheList: ndn_chart_data, + ndnCacheUtilList: ndn_util_chart_data, + count, + ndnCount + }); + }) + .fail(() => { + this.resetChartData(); + }); + } + + startCacheRefresh() { + this.setState({ + cache_refresh: setInterval(this.refreshCache, 2000), + }); + } + + stopCacheRefresh() { + clearInterval(this.state.cache_refresh); + } + + render() { + let chartColor = ChartThemeColor.green; + let ndnChartColor = ChartThemeColor.green; + let ndnUtilColor = ChartThemeColor.green; + let dbcachehit = 0; + let ndncachehit = 0; + let ndncachemax = 0; + let ndncachecurr = 0; + let utilratio = 0; + let content = ( +
+ + + {_("Loading database monitor information ...")} + + + +
+ ); + + if (!this.state.loading) { + dbcachehit = parseInt(this.state.data.dbcachehitratio[0]); + ndncachehit = parseInt(this.state.data.normalizeddncachehitratio[0]); + ndncachemax = parseInt(this.state.data.maxnormalizeddncachesize[0]); + ndncachecurr = parseInt(this.state.data.currentnormalizeddncachesize[0]); + utilratio = Math.round((ndncachecurr / ndncachemax) * 100); + if (utilratio === 0) { + // Just round up to 1 + utilratio = 1; + } + + // Database cache + if (dbcachehit > 89) { + chartColor = ChartThemeColor.green; + } else if (dbcachehit > 74) { + chartColor = ChartThemeColor.orange; + } else { + chartColor = ChartThemeColor.purple; + } + // NDN cache ratio + if (ndncachehit > 89) { + ndnChartColor = ChartThemeColor.green; + } else if (ndncachehit > 74) { + ndnChartColor = ChartThemeColor.orange; + } else { + ndnChartColor = ChartThemeColor.purple; + } + // NDN cache utilization + if (utilratio > 95) { + ndnUtilColor = ChartThemeColor.purple; + } else if (utilratio > 90) { + ndnUtilColor = ChartThemeColor.orange; + } else { + ndnUtilColor = ChartThemeColor.green; + } + + content = ( + + {_("Normalized DN Cache")}}> +
+ + + + +
+
+ + + {_("Cache Hit Ratio")} + + + + + {ndncachehit}% + + +
+
+ `${datum.name}: ${datum.y}`} constrainToVisibleArea />} + height={200} + maxDomain={{ y: 100 }} + minDomain={{ y: 0 }} + padding={{ + bottom: 40, + left: 60, + top: 10, + right: 15, + }} + width={350} + themeColor={ndnChartColor} + > + + + + + + +
+
+
+
+
+ + + +
+
+ + + {_("Cache Utilization")} + + + + + {utilratio}% + + + + + {_("Cached DN's")} + + + {numToCommas(this.state.data.currentnormalizeddncachecount[0])} +
+
+ `${datum.name}: ${datum.y}`} constrainToVisibleArea />} + height={200} + maxDomain={{ y: 100 }} + minDomain={{ y: 0 }} + padding={{ + bottom: 40, + left: 60, + top: 10, + right: 15, + }} + width={350} + themeColor={ndnUtilColor} + > + + + + + + +
+
+
+
+
+
+ + + + {_("NDN Cache Hit Ratio:")} + + + {this.state.data.normalizeddncachehitratio}% + + + {_("NDN Cache Max Size:")} + + + {displayBytes(this.state.data.maxnormalizeddncachesize)} + + + {_("NDN Cache Tries:")} + + + {numToCommas(this.state.data.normalizeddncachetries)} + + + {_("NDN Current Cache Size:")} + + + {displayBytes(this.state.data.currentnormalizeddncachesize)} + + + {_("NDN Cache Hits:")} + + + {numToCommas(this.state.data.normalizeddncachehits)} + + + {_("NDN Cache DN Count:")} + + + {numToCommas(this.state.data.currentnormalizeddncachecount)} + + + {_("NDN Cache Evictions:")} + + + {numToCommas(this.state.data.normalizeddncacheevictions)} + + + {_("NDN Cache Thread Size:")} + + + {numToCommas(this.state.data.normalizeddncachethreadsize)} + + + {_("NDN Cache Thread Slots:")} + + + {numToCommas(this.state.data.normalizeddncachethreadslots)} + + +
+
+
+ ); + } + + return ( +
+ + + {_("Database Performance Statistics")} + + +
+ {content} +
+ +
+ ); + } +} + +// Prop types and defaults + +DatabaseMonitorMDB.propTypes = { + data: PropTypes.object, + serverId: PropTypes.string, + enableTree: PropTypes.func, +}; + +DatabaseMonitorMDB.defaultProps = { + data: {}, + serverId: "", +}; diff --git a/src/cockpit/389-console/src/lib/monitor/suffixMonitor.jsx b/src/cockpit/389-console/src/lib/monitor/suffixMonitor.jsx index 4641377317..ec78dbdc21 100644 --- a/src/cockpit/389-console/src/lib/monitor/suffixMonitor.jsx +++ b/src/cockpit/389-console/src/lib/monitor/suffixMonitor.jsx @@ -626,4 +626,365 @@ SuffixMonitor.defaultProps = { bename: "", }; -export default SuffixMonitor; +export class SuffixMonitorMDB extends React.Component { + constructor (props) { + super(props); + this.state = { + activeTabKey: 0, + data: {}, + loading: true, + // refresh charts + cache_refresh: "", + count: 10, + utilCount: 5, + entryCacheList: [], + entryUtilCacheList: [], + }; + + // Toggle currently active tab + this.handleNavSelect = (event, tabIndex) => { + this.setState({ + activeTabKey: tabIndex + }); + }; + + this.startCacheRefresh = this.startCacheRefresh.bind(this); + this.refreshSuffixCache = this.refreshSuffixCache.bind(this); + } + + componentDidMount() { + this.resetChartData(); + this.refreshSuffixCache(); + this.startCacheRefresh(); + this.props.enableTree(); + } + + componentWillUnmount() { + this.stopCacheRefresh(); + } + + resetChartData() { + this.setState({ + data: { + // Entry cache + entrycachehitratio: [0], + entrycachetries: [0], + entrycachehits: [0], + maxentrycachesize: [0], + currententrycachesize: [0], + maxentrycachecount: [0], + currententrycachecount: [0], + }, + entryCacheList: [ + { name: "", x: "1", y: 0 }, + { name: "", x: "2", y: 0 }, + { name: "", x: "3", y: 0 }, + { name: "", x: "4", y: 0 }, + { name: "", x: "5", y: 0 }, + { name: "", x: "6", y: 0 }, + { name: "", x: "7", y: 0 }, + { name: "", x: "8", y: 0 }, + { name: "", x: "9", y: 0 }, + { name: "", x: "10", y: 0 }, + ], + }); + } + + refreshSuffixCache() { + // Search for db cache stat and update state + const cmd = [ + "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket", + "monitor", "backend", this.props.suffix + ]; + log_cmd("refreshSuffixCache", "Get suffix monitor", cmd); + cockpit + .spawn(cmd, { superuser: true, err: "message" }) + .done(content => { + const config = JSON.parse(content); + let count = this.state.count + 1; + const utilCount = this.state.utilCount + 1; + if (count > 100) { + // Keep progress count in check + count = 1; + } + + // Build up the Entry Cache chart data + const entryRatio = config.attrs.entrycachehitratio[0]; + const entry_data = this.state.entryCacheList; + entry_data.shift(); + entry_data.push({ name: _("Cache Hit Ratio"), x: count.toString(), y: parseInt(entryRatio) }); + + // Build up the Entry Util chart data + const entry_util_data = this.state.entryUtilCacheList; + let maxsize = config.attrs.maxentrycachesize[0]; + let currsize = config.attrs.currententrycachesize[0]; + let utilratio = Math.round((currsize / maxsize) * 100); + if (utilratio === 0) { + utilratio = 1; + } + entry_util_data.shift(); + entry_util_data.push({ name: _("Cache Utilization"), x: utilCount.toString(), y: parseInt(utilratio) }); + + this.setState({ + data: config.attrs, + loading: false, + entryCacheList: entry_data, + entryUtilCacheList: entry_util_data, + count, + utilCount + }); + }) + .fail(() => { + this.resetChartData(); + }); + } + + startCacheRefresh() { + this.setState({ + cache_refresh: setInterval(this.refreshSuffixCache, 2000) + }); + } + + stopCacheRefresh() { + clearInterval(this.state.cache_refresh); + } + + render() { + let entryChartColor = ChartThemeColor.green; + let entryUtilChartColor = ChartThemeColor.green; + let cachehit = 1; + let cachemax = 0; + let cachecurr = 0; + let cachecount = 0; + let utilratio = 1; + let SuffixIcon = TreeIcon; + + if (this.props.dbtype === "subsuffix") { + SuffixIcon = LeafIcon; + } + + let content = ( +
+ + + {_("Loading Suffix Monitor Information ...")} + + + +
+ ); + + if (!this.state.loading) { + // Entry cache + cachehit = parseInt(this.state.data.entrycachehitratio[0]); + cachemax = parseInt(this.state.data.maxentrycachesize[0]); + cachecurr = parseInt(this.state.data.currententrycachesize[0]); + cachecount = parseInt(this.state.data.currententrycachecount[0]); + utilratio = Math.round((cachecurr / cachemax) * 100); + + // Adjust ratios if needed + if (utilratio === 0) { + utilratio = 1; + } + + // Entry cache chart color + if (cachehit > 89) { + entryChartColor = ChartThemeColor.green; + } else if (cachehit > 74) { + entryChartColor = ChartThemeColor.orange; + } else { + entryChartColor = ChartThemeColor.purple; + } + // Entry cache utilization + if (utilratio > 95) { + entryUtilChartColor = ChartThemeColor.purple; + } else if (utilratio > 90) { + entryUtilChartColor = ChartThemeColor.orange; + } else { + entryUtilChartColor = ChartThemeColor.green; + } + + content = ( +
+ + {_("Entry Cache")}}> +
+ + + + +
+
+ + + {_("Cache Hit Ratio")} + + + + + {cachehit}% + + +
+
+ `${datum.name}: ${datum.y}`} constrainToVisibleArea />} + height={200} + maxDomain={{ y: 100 }} + minDomain={{ y: 0 }} + padding={{ + bottom: 40, + left: 60, + top: 10, + right: 15, + }} + width={350} + themeColor={entryChartColor} + > + + + + + + +
+
+
+
+
+ + + +
+
+ + + {_("Cache Utilization")} + + + + + {utilratio}% + + + + + {_("Cached Entries")} + + + {cachecount} +
+
+ `${datum.name}: ${datum.y}`} constrainToVisibleArea />} + height={200} + maxDomain={{ y: 100 }} + minDomain={{ y: 0 }} + padding={{ + bottom: 40, + left: 60, + top: 10, + right: 15, + }} + width={350} + themeColor={entryUtilChartColor} + > + + + + + + +
+
+
+
+
+
+
+ + + {_("Entry Cache Hit Ratio:")} + + + {this.state.data.entrycachehitratio[0]}% + + + {_("Entry Cache Max Size:")} + + + {displayBytes(cachemax)} + + + + {_("Entry Cache Hits:")} + + + {numToCommas(this.state.data.entrycachehits[0])} + + + {_("Entry Cache Current Size:")} + + + {displayBytes(cachecurr)} + + + {_("Entry Cache Tries:")} + + + {numToCommas(this.state.data.entrycachetries[0])} + + + {_("Entry Cache Max Entries:")} + + + {numToCommas(this.state.data.maxentrycachecount[0])} + + + {_("Entry Cache Count:")} + + + {numToCommas(this.state.data.currententrycachecount[0])} + + +
+
+
+ ); + } + + return ( +
+ + + {this.props.suffix} ({this.props.bename}) + + +
+ {content} +
+
+ ); + } +} + +SuffixMonitorMDB.propTypes = { + serverId: PropTypes.string, + suffix: PropTypes.string, + bename: PropTypes.string, + enableTree: PropTypes.func, +}; + +SuffixMonitorMDB.defaultProps = { + serverId: "", + suffix: "", + bename: "", +}; diff --git a/src/cockpit/389-console/src/monitor.jsx b/src/cockpit/389-console/src/monitor.jsx index d4ee265ed7..a4d07eb2f5 100644 --- a/src/cockpit/389-console/src/monitor.jsx +++ b/src/cockpit/389-console/src/monitor.jsx @@ -3,8 +3,8 @@ import React from "react"; import { log_cmd } from "./lib/tools.jsx"; import PropTypes from "prop-types"; import ServerMonitor from "./lib/monitor/serverMonitor.jsx"; -import DatabaseMonitor from "./lib/monitor/dbMonitor.jsx"; -import SuffixMonitor from "./lib/monitor/suffixMonitor.jsx"; +import { DatabaseMonitor, DatabaseMonitorMDB } from "./lib/monitor/dbMonitor.jsx"; +import { SuffixMonitor, SuffixMonitorMDB } from "./lib/monitor/suffixMonitor.jsx"; import ChainingMonitor from "./lib/monitor/chainingMonitor.jsx"; import AccessLogMonitor from "./lib/monitor/accesslog.jsx"; import AuditLogMonitor from "./lib/monitor/auditlog.jsx"; @@ -34,6 +34,8 @@ import { const _ = cockpit.gettext; +const BE_IMPL_MDB = "mdb"; + export class Monitor extends React.Component { constructor(props) { super(props); @@ -79,6 +81,8 @@ export class Monitor extends React.Component { auditlogLocation: "", auditfaillogLocation: "", securitylogLocation: "", + // DB engine, bdb or mdb (default) + dbEngine: BE_IMPL_MDB, }; // Bindings @@ -95,6 +99,7 @@ export class Monitor extends React.Component { this.loadMonitorChaining = this.loadMonitorChaining.bind(this); this.loadDiskSpace = this.loadDiskSpace.bind(this); this.reloadDisks = this.reloadDisks.bind(this); + this.getDBEngine = this.getDBEngine.bind(this); // Replication this.onHandleLoadMonitorReplication = this.onHandleLoadMonitorReplication.bind(this); this.loadCleanTasks = this.loadCleanTasks.bind(this); @@ -111,6 +116,10 @@ export class Monitor extends React.Component { this.loadMonitor = this.loadMonitor.bind(this); } + componentDidMount() { + this.getDBEngine(); + } + componentDidUpdate(prevProps) { if (this.props.wasActiveList.includes(6)) { if (this.state.firstLoad) { @@ -573,6 +582,25 @@ export class Monitor extends React.Component { }); } + getDBEngine () { + const cmd = [ + "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket", + "backend", "config", "get" + ]; + log_cmd("getDBEngine", "Get DB Implementation", cmd); + cockpit + .spawn(cmd, { superuser: true, err: "message" }) + .done(content => { + const config = JSON.parse(content); + const attrs = config.attrs; + if ('nsslapd-backend-implement' in attrs) { + this.setState({ + dbEngine: attrs['nsslapd-backend-implement'][0], + }); + } + }); + } + reloadSNMP() { const cmd = [ "dsconf", "-j", "ldapi://%2fvar%2frun%2fslapd-" + this.props.serverId + ".socket", @@ -948,13 +976,24 @@ export class Monitor extends React.Component {
); } else { - monitor_element = ( - - ); + if (this.state.dbEngine === BE_IMPL_MDB) { + monitor_element = ( + + ); + } else { + monitor_element = ( + + ); + } + } } else if (this.state.node_name === "server-monitor") { if (this.state.serverLoading) { @@ -1133,16 +1172,29 @@ export class Monitor extends React.Component { ); } else { // Suffix - monitor_element = ( - - ); + if (this.state.dbEngine === BE_IMPL_MDB) { + monitor_element = ( + + ); + } else { + monitor_element = ( + + ); + } } } }