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"