diff --git a/pkg/metrics/metrics.jsx b/pkg/metrics/metrics.jsx index c2095fe4abbf..f60c4c9a2550 100644 --- a/pkg/metrics/metrics.jsx +++ b/pkg/metrics/metrics.jsx @@ -1101,7 +1101,20 @@ class MetricsMinute extends React.Component { }); let desc; - if (this.props.isExpanded && this.props.events) { + if (this.props.isExpanded && this.props.booted) { + const timestamp = this.props.startTime + (this.props.minute * 60000); + desc = ( +
+ + + + + {_("Boot")} + + +
+ ); + } else if (this.props.isExpanded && this.props.events) { const timestamp = this.props.startTime + (this.props.minute * 60000); const logsPanel = ( @@ -1170,6 +1183,7 @@ class MetricsHour extends React.Component { if (this.state.dataItems !== nextProps.data.length || this.state.isHourExpanded !== nextState.isHourExpanded || this.props.startTime !== nextProps.startTime || + this.props.boots !== nextProps.boots || Object.keys(this.props.selectedVisibility).some(itm => this.props.selectedVisibility[itm] != nextProps.selectedVisibility[itm])) { this.updateGraphs(nextProps.data, nextProps.startTime, nextProps.selectedVisibility, nextState.isHourExpanded); return false; @@ -1237,6 +1251,8 @@ class MetricsHour extends React.Component { const dataOffset = minute * SAMPLES_PER_MIN; const dataSlice = normData.slice(dataOffset, dataOffset + SAMPLES_PER_MIN); const rawSlice = this.props.data.slice(dataOffset, dataOffset + SAMPLES_PER_MIN); + const is_boot = this.props.boots.includes(minute); + minuteGraphs.push( + selectedVisibility={selectedVisibility} + booted={is_boot} /> ); } @@ -1559,7 +1576,8 @@ class MetricsHistory extends React.Component { packagekitExists: false, isBeibootBridge: false, isPythonPCPInstalled: null, - selectedVisibility: this.columns.reduce((a, v) => ({ ...a, [v[0]]: true }), {}) + selectedVisibility: this.columns.reduce((a, v) => ({ ...a, [v[0]]: true }), {}), + boots: [], // journalctl --list-boots as [{started: Date, ended: Date}] }; this.handleMoreData = this.handleMoreData.bind(this); @@ -1622,7 +1640,23 @@ class MetricsHistory extends React.Component { const isBeibootBridge = cmdline?.includes("ic# cockpit-bridge"); - this.setState({ packagekitExists, isBeibootBridge }); + let boots = []; + try { + // Only 14 days of metrics are shown + const output = await cockpit.spawn(["journalctl", "--list-boots", "--since", "-15d", "--output", "json"]); + const list_boots = JSON.parse(output); + boots = list_boots.map(boot => { + return { + started: new Date(boot?.first_entry / 1000), + ended: new Date(boot?.last_entry / 1000), + current_boot: boot?.index === 0, + }; + }); + } catch (exc) { + console.warn("journalctl --list-boots failed", exc); + } + + this.setState({ packagekitExists, isBeibootBridge, boots }); } handleMoreData() { @@ -1927,6 +1961,11 @@ class MetricsHistory extends React.Component { { this.state.hours.map((time, i) => { + const date_time = new Date(time); + const boot_minutes = this.state.boots.filter(reboot => reboot.started.getDay() === date_time.getDay() && + reboot.started.getYear() === date_time.getYear() && + reboot.started.getHours() === date_time.getHours()) + .map(reboot => reboot.started.getMinutes()); const showHeader = i == 0 || timeformat.date(time) != timeformat.date(this.state.hours[i - 1]); return ( @@ -1934,7 +1973,8 @@ class MetricsHistory extends React.Component { {showHeader && } + data={this.data[time]} clipLeading={i == 0} + boots={boot_minutes} /> ); })} diff --git a/test/verify/check-metrics b/test/verify/check-metrics index c67d1a617613..f4aca5a1cad3 100755 --- a/test/verify/check-metrics +++ b/test/verify/check-metrics @@ -445,9 +445,15 @@ class TestHistoryMetrics(testlib.MachineCase): b.reload() b.enter_page("/metrics") - # details for above load event + # Expand metrics when loaded b.wait_in_text(".metrics-heading", "CPU") b.click("#metrics-hour-1615197600000 button.metrics-events-expander") + + # Show boot as event + with b.wait_timeout(60): + b.wait_in_text(".metrics-minute[data-minute='35']", "Boot") + + # details for above load event b.click(f"{load_minute_sel} .metrics-events button.spikes_info") b.wait_in_text(".cockpit-log-panel", "load-hog") action = "Stopping" if load_minute == 39 else "Started"