grafana/public/app/features/dashboard/dashgrid/liveTimer.ts

114 lines
3.0 KiB
TypeScript

import { dateMath, dateTime, TimeRange } from '@grafana/data';
import { PanelChrome } from './PanelChrome';
// target is 20hz (50ms), but we poll at 100ms to smooth out jitter
const interval = 100;
interface LiveListener {
last: number;
intervalMs: number;
panel: PanelChrome;
}
class LiveTimer {
listeners: LiveListener[] = [];
budget = 1;
threshold = 1.5; // trial and error appears about right
ok = true;
lastUpdate = Date.now();
isLive = false; // the dashboard time range ends in "now"
timeRange?: TimeRange;
liveTimeOffset = 0;
/** Called when the dashboard time range changes */
setLiveTimeRange(v?: TimeRange) {
this.timeRange = v;
this.isLive = v?.raw?.to === 'now';
if (this.isLive) {
const from = dateMath.parse(v!.raw.from, false)?.valueOf()!;
const to = dateMath.parse(v!.raw.to, true)?.valueOf()!;
this.liveTimeOffset = to - from;
for (const listener of this.listeners) {
listener.intervalMs = getLiveTimerInterval(this.liveTimeOffset, listener.panel.props.width);
}
}
}
listen(panel: PanelChrome) {
this.listeners.push({
last: this.lastUpdate,
panel: panel,
intervalMs: getLiveTimerInterval(
60000, // 1min
panel.props.width
),
});
}
remove(panel: PanelChrome) {
this.listeners = this.listeners.filter((v) => v.panel !== panel);
}
updateInterval(panel: PanelChrome) {
if (!this.timeRange || !this.isLive) {
return;
}
for (const listener of this.listeners) {
if (listener.panel === panel) {
listener.intervalMs = getLiveTimerInterval(this.liveTimeOffset, listener.panel.props.width);
return;
}
}
}
// Called at the consistent dashboard interval
measure = () => {
const now = Date.now();
this.budget = (now - this.lastUpdate) / interval;
this.ok = this.budget <= this.threshold;
this.lastUpdate = now;
// For live dashboards, listen to changes
if (this.ok && this.isLive && this.timeRange) {
// when the time-range is relative fire events
let tr: TimeRange | undefined = undefined;
for (const listener of this.listeners) {
if (!listener.panel.props.isInView) {
continue;
}
const elapsed = now - listener.last;
if (elapsed >= listener.intervalMs) {
if (!tr) {
const { raw } = this.timeRange;
tr = {
raw,
from: dateTime(now - this.liveTimeOffset),
to: dateTime(now),
};
}
listener.panel.liveTimeChanged(tr);
listener.last = now;
}
}
}
};
}
const FIVE_MINS = 5 * 60 * 1000;
export function getLiveTimerInterval(delta: number, width: number): number {
const millisPerPixel = Math.ceil(delta / width / 100) * 100;
if (millisPerPixel > FIVE_MINS) {
return FIVE_MINS;
}
return millisPerPixel;
}
export const liveTimer = new LiveTimer();
setInterval(liveTimer.measure, interval);