Chart updates to improve stability, possible fix for infinite loop (#18329)

Co-authored-by: Bram Kragten <mail@bramkragten.nl>
This commit is contained in:
karwosts 2023-10-24 13:41:34 -07:00 committed by GitHub
parent 10bcaadcdb
commit 3e6ab8b179
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 83 additions and 31 deletions

View File

@ -12,6 +12,7 @@ import { styleMap } from "lit/directives/style-map";
import { clamp } from "../../common/number/clamp";
import { computeRTL } from "../../common/util/compute_rtl";
import { HomeAssistant } from "../../types";
import { debounce } from "../../common/util/debounce";
export const MIN_TIME_BETWEEN_UPDATES = 60 * 5 * 1000;
@ -52,6 +53,12 @@ export class HaChartBase extends LitElement {
@state() private _hiddenDatasets: Set<number> = new Set();
private _paddingUpdateCount = 0;
private _paddingUpdateLock = false;
private _paddingYAxisInternal = 0;
public disconnectedCallback() {
super.disconnectedCallback();
this._releaseCanvas();
@ -104,9 +111,44 @@ export class HaChartBase extends LitElement {
});
}
public shouldUpdate(changedProps: PropertyValues): boolean {
if (
this._paddingUpdateLock &&
changedProps.size === 1 &&
changedProps.has("paddingYAxis")
) {
return false;
}
return true;
}
private _debouncedClearUpdates = debounce(
() => {
this._paddingUpdateCount = 0;
},
2000,
false
);
public willUpdate(changedProps: PropertyValues): void {
super.willUpdate(changedProps);
if (!this._paddingUpdateLock) {
this._paddingYAxisInternal = this.paddingYAxis;
if (changedProps.size === 1 && changedProps.has("paddingYAxis")) {
this._paddingUpdateCount++;
if (this._paddingUpdateCount > 300) {
this._paddingUpdateLock = true;
// eslint-disable-next-line
console.error(
"Detected excessive chart padding updates, possibly an infinite loop. Disabling axis padding."
);
} else {
this._debouncedClearUpdates();
}
}
}
if (!this.hasUpdated || !this.chart) {
return;
}
@ -171,10 +213,10 @@ export class HaChartBase extends LitElement {
this.height ?? this._chartHeight ?? this.clientWidth / 2
}px`,
"padding-left": `${
computeRTL(this.hass) ? 0 : this.paddingYAxis
computeRTL(this.hass) ? 0 : this._paddingYAxisInternal
}px`,
"padding-right": `${
computeRTL(this.hass) ? this.paddingYAxis : 0
computeRTL(this.hass) ? this._paddingYAxisInternal : 0
}px`,
})}
>
@ -324,7 +366,7 @@ export class HaChartBase extends LitElement {
clamp(
context.tooltip.caretX,
100,
this.clientWidth - 100 - this.paddingYAxis
this.clientWidth - 100 - this._paddingYAxisInternal
) -
100 +
"px",

View File

@ -73,9 +73,9 @@ export class StateHistoryCharts extends LitElement {
@property({ type: Boolean }) public isLoadingData = false;
@state() private _computedStartTime!: Date;
private _computedStartTime!: Date;
@state() private _computedEndTime!: Date;
private _computedEndTime!: Date;
@state() private _maxYWidth = 0;
@ -114,31 +114,6 @@ export class StateHistoryCharts extends LitElement {
${this.hass.localize("ui.components.history_charts.no_history_found")}
</div>`;
}
const now = new Date();
this._computedEndTime =
this.upToNow || !this.endTime || this.endTime > now ? now : this.endTime;
if (this.startTime) {
this._computedStartTime = this.startTime;
} else if (this.hoursToShow) {
this._computedStartTime = new Date(
new Date().getTime() - 60 * 60 * this.hoursToShow * 1000
);
} else {
this._computedStartTime = new Date(
this.historyData.timeline.reduce(
(minTime, stateInfo) =>
Math.min(
minTime,
new Date(stateInfo.data[0].last_changed).getTime()
),
new Date().getTime()
)
);
}
const combinedItems = this.historyData.timeline.length
? (this.virtualize
? chunkData(this.historyData.timeline, CANVAS_TIMELINE_ROWS_CHUNK)
@ -220,10 +195,45 @@ export class StateHistoryCharts extends LitElement {
return true;
}
protected willUpdate() {
protected willUpdate(changedProps: PropertyValues) {
if (!this.hasUpdated) {
loadVirtualizer();
}
if (
[...changedProps.keys()].some(
(prop) =>
!(
["_maxYWidth", "_childYWidths", "_chartCount"] as PropertyKey[]
).includes(prop)
)
) {
// Don't recompute times when we just want to update layout
const now = new Date();
this._computedEndTime =
this.upToNow || !this.endTime || this.endTime > now
? now
: this.endTime;
if (this.startTime) {
this._computedStartTime = this.startTime;
} else if (this.hoursToShow) {
this._computedStartTime = new Date(
new Date().getTime() - 60 * 60 * this.hoursToShow * 1000
);
} else {
this._computedStartTime = new Date(
this.historyData.timeline.reduce(
(minTime, stateInfo) =>
Math.min(
minTime,
new Date(stateInfo.data[0].last_changed).getTime()
),
new Date().getTime()
)
);
}
}
}
protected updated(changedProps: PropertyValues) {