mirror of
https://github.com/grafana/grafana.git
synced 2025-02-12 08:35:43 -06:00
* Fix: make webpack pickup workers written in TS * Add comlink to dependencies * Temporary fix: copy paste `toDataQueryError` from @grafana/runtime to avoid web dependencies * Implemented comlink-based centrifuge worker & worker proxy * Temporary fix: implement comlink transferHandlers for subscriptions and streamingdataframes * Move liveTimer filtering from CentrifugeService into GrafanaLiveService * Switch from CentrifugeService to CentrifugeServiceWorkerProxy in GrafanaLive * Naming fix * Refactor: move liveTimer-based data filtering from GrafanaLiveService to CentrifugeServiceWorker * observe dataStream on an async scheduler * Fix: - Unsubscribe is now propagated from the main thread to the worker, - improve worker&workerProxy types * Fix: Prettify types * Fix: Add error & complete observers * Docs: Add comment explaining the `subscriberTransferHandler` * Fix: Replace `StreamingDataFrameHandler` with explicitly converting StreamingDataFrame to a DataFrameDTO * Refactor: move liveTimer filtering to service.ts to make it easy to implement a `live-service-web-worker` feature flag * Feat: add `live-service-web-worker` feature flag * Fix: extract toDataQueryError.ts to a separate file within `@grafana-runtime` to avoid having a dependency from webworker to the whole package (@grafana-runtime/index.ts) * Update public/app/features/dashboard/dashgrid/liveTimer.ts Co-authored-by: Leon Sorokin <leeoniya@gmail.com> * Fix: fixed default import class in worker file * Fix: cast worker as Endpoint * Migrate from worker-loader to webpack native worker support v1 - broken prod build * Fix: Use custom path in HtmlWebpackPlugin * Fix: Loading workers from CDNs * Fix: Avoid issues with jest ESM support by mocking `createWorker` files * Fix: move the custom mockWorker rendering layout to `test/mocks` Co-authored-by: Leon Sorokin <leeoniya@gmail.com>
120 lines
3.1 KiB
TypeScript
120 lines
3.1 KiB
TypeScript
import { dateMath, dateTime, TimeRange } from '@grafana/data';
|
|
import { BehaviorSubject } from 'rxjs';
|
|
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 = new BehaviorSubject(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;
|
|
|
|
const oldOk = this.ok.getValue();
|
|
const newOk = this.budget <= this.threshold;
|
|
if (oldOk !== newOk) {
|
|
this.ok.next(newOk);
|
|
}
|
|
this.lastUpdate = now;
|
|
|
|
// For live dashboards, listen to changes
|
|
if (this.isLive && this.ok.getValue() && 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);
|