From 92c8de307db0f78aedf32d4397f4b9694cdcf73c Mon Sep 17 00:00:00 2001 From: Zack Barett Date: Tue, 30 Aug 2022 20:45:37 -0500 Subject: [PATCH] Add CPU and Memory Graphs to hardware page (#13399) * CPU and Memory Graphs * localize * Comments * Always show the graphs * Use Subscribe Mixin --- src/data/hardware.ts | 8 + .../config/hardware/ha-config-hardware.ts | 202 +++++++++++++++++- src/translations/en.json | 2 + 3 files changed, 205 insertions(+), 7 deletions(-) diff --git a/src/data/hardware.ts b/src/data/hardware.ts index 7f0cfc755a..f612512161 100644 --- a/src/data/hardware.ts +++ b/src/data/hardware.ts @@ -37,3 +37,11 @@ export interface HardwareInfoBoardInfo { revision?: string; hassio_board_id?: string; } + +export interface SystemStatusStreamMessage { + cpu_percent: number; + memory_free_mb: number; + memory_used_mb: number; + memory_used_percent: number; + timestamp: string; +} diff --git a/src/panels/config/hardware/ha-config-hardware.ts b/src/panels/config/hardware/ha-config-hardware.ts index ebeea3e350..14795a97e3 100644 --- a/src/panels/config/hardware/ha-config-hardware.ts +++ b/src/panels/config/hardware/ha-config-hardware.ts @@ -1,18 +1,26 @@ import "@material/mwc-list/mwc-list"; import "@material/mwc-list/mwc-list-item"; import { mdiDotsVertical } from "@mdi/js"; +import type { ChartOptions } from "chart.js"; import { css, html, LitElement, PropertyValues, TemplateResult } from "lit"; import { customElement, property, state } from "lit/decorators"; import { ifDefined } from "lit/directives/if-defined"; import { isComponentLoaded } from "../../../common/config/is_component_loaded"; +import { numberFormatToLocale } from "../../../common/number/format_number"; +import { round } from "../../../common/number/round"; import "../../../components/buttons/ha-progress-button"; +import "../../../components/chart/ha-chart-base"; import "../../../components/ha-alert"; import "../../../components/ha-button-menu"; import "../../../components/ha-card"; import "../../../components/ha-clickable-list-item"; import "../../../components/ha-icon-next"; import "../../../components/ha-settings-row"; -import { BOARD_NAMES, HardwareInfo } from "../../../data/hardware"; +import { + BOARD_NAMES, + HardwareInfo, + SystemStatusStreamMessage, +} from "../../../data/hardware"; import { extractApiErrorMessage, ignoreSupervisorError, @@ -30,14 +38,27 @@ import { showConfirmationDialog, } from "../../../dialogs/generic/show-dialog-box"; import "../../../layouts/hass-subpage"; +import { SubscribeMixin } from "../../../mixins/subscribe-mixin"; +import { DEFAULT_PRIMARY_COLOR } from "../../../resources/ha-style"; import { haStyle } from "../../../resources/styles"; import type { HomeAssistant } from "../../../types"; import { hardwareBrandsUrl } from "../../../util/brands-url"; import { showToast } from "../../../util/toast"; import { showhardwareAvailableDialog } from "./show-dialog-hardware-available"; +const DATASAMPLES = 60; + +const DATA_SET_CONFIG = { + fill: "origin", + borderColor: DEFAULT_PRIMARY_COLOR, + backgroundColor: DEFAULT_PRIMARY_COLOR + "2B", + pointRadius: 0, + lineTension: 0.2, + borderWidth: 1, +}; + @customElement("ha-config-hardware") -class HaConfigHardware extends LitElement { +class HaConfigHardware extends SubscribeMixin(LitElement) { @property({ attribute: false }) public hass!: HomeAssistant; @property({ type: Boolean }) public narrow!: boolean; @@ -50,9 +71,101 @@ class HaConfigHardware extends LitElement { @state() private _hardwareInfo?: HardwareInfo; + @state() private _chartOptions?: ChartOptions; + + @state() private _systemStatusData?: SystemStatusStreamMessage; + + private _memoryEntries: { x: number; y: number | null }[] = []; + + private _cpuEntries: { x: number; y: number | null }[] = []; + + public hassSubscribe() { + return [ + this.hass.connection.subscribeMessage( + (message) => { + // Only store the last 60 entries + this._memoryEntries.shift(); + this._cpuEntries.shift(); + + this._memoryEntries.push({ + x: new Date(message.timestamp).getTime(), + y: message.memory_used_percent, + }); + this._cpuEntries.push({ + x: new Date(message.timestamp).getTime(), + y: message.cpu_percent, + }); + + this._systemStatusData = message; + }, + { + type: "hardware/subscribe_system_status", + } + ), + ]; + } + + protected willUpdate(): void { + if (!this.hasUpdated) { + this._chartOptions = { + animation: false, + responsive: true, + scales: { + y: { + gridLines: { + drawTicks: false, + }, + ticks: { + maxTicksLimit: 7, + fontSize: 10, + max: 100, + min: 0, + stepSize: 1, + callback: (value) => value + "%", + }, + }, + x: { + type: "time", + adapters: { + date: { + locale: this.hass.locale, + }, + }, + gridLines: { + display: true, + drawTicks: false, + }, + ticks: { + maxRotation: 0, + sampleSize: 5, + autoSkipPadding: 20, + major: { + enabled: true, + }, + fontSize: 10, + autoSkip: true, + maxTicksLimit: 5, + }, + }, + }, + // @ts-expect-error + locale: numberFormatToLocale(this.hass.locale), + }; + } + } + protected firstUpdated(changedProps: PropertyValues) { super.firstUpdated(changedProps); this._load(); + + const date = new Date(); + // Force graph to start drawing from the right + for (let i = 0; i < DATASAMPLES; i++) { + const t = new Date(date); + t.setSeconds(t.getSeconds() - 5 * (DATASAMPLES - i)); + this._memoryEntries.push({ x: t.getTime(), y: null }); + this._cpuEntries.push({ x: t.getTime(), y: null }); + } } protected render(): TemplateResult { @@ -118,9 +231,9 @@ class HaConfigHardware extends LitElement { > ` : ""} - ${boardName - ? html` -
+
+ ${boardName + ? html`
@@ -169,9 +282,68 @@ class HaConfigHardware extends LitElement {
+ ` + : ""} + + +
+
+ ${this.hass.localize("ui.panel.config.hardware.processor")}
- ` - : ""} +
+ ${this._systemStatusData?.cpu_percent || "-"}% +
+
+
+ +
+
+ +
+
+ ${this.hass.localize("ui.panel.config.hardware.memory")} +
+
+ ${this._systemStatusData + ? html` + ${round(this._systemStatusData.memory_used_mb / 1024, 1)} + GB / + ${round( + (this._systemStatusData.memory_used_mb! + + this._systemStatusData.memory_free_mb!) / + 1024, + 0 + )} + GB + ` + : "- GB / - GB"} +
+
+
+ +
+
+
`; } @@ -280,6 +452,7 @@ class HaConfigHardware extends LitElement { justify-content: space-between; flex-direction: column; display: flex; + margin-bottom: 16px; } .card-content { display: flex; @@ -298,6 +471,21 @@ class HaConfigHardware extends LitElement { .secondary-text { font-size: 14px; } + + .header { + padding: 16px; + display: flex; + justify-content: space-between; + } + + .header .title { + color: var(--secondary-text-color); + font-size: 18px; + } + + .header .value { + font-size: 16px; + } `, ]; } diff --git a/src/translations/en.json b/src/translations/en.json index 8e96a7d3f9..4b9bc48ad1 100755 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -1620,6 +1620,8 @@ "id": "ID", "attributes": "Attributes" }, + "processor": "Processor", + "memory": "Memory", "reboot_host": "Reboot host", "rebooting_host": "Rebooting host", "reboot_host_confirm": "Are you sure you want to reboot your host?",