mirror of
https://github.com/grafana/grafana.git
synced 2025-09-26 12:04:20 +08:00
Live: remove measurement controller (#32622)
This commit is contained in:
@ -1,77 +0,0 @@
|
||||
import { FieldType } from '@grafana/data';
|
||||
import { MeasurementCollector } from './collector';
|
||||
|
||||
describe('MeasurementCollector', () => {
|
||||
it('should collect values', () => {
|
||||
const collector = new MeasurementCollector();
|
||||
collector.addBatch({
|
||||
batch: [
|
||||
{
|
||||
key: 'aaa',
|
||||
schema: {
|
||||
fields: [
|
||||
{ name: 'time', type: FieldType.time },
|
||||
{ name: 'value', type: FieldType.number },
|
||||
],
|
||||
},
|
||||
data: {
|
||||
values: [
|
||||
[100, 200],
|
||||
[1, 2],
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
key: 'aaa',
|
||||
data: { values: [[300], [3]] },
|
||||
},
|
||||
{
|
||||
key: 'aaa',
|
||||
data: { values: [[400], [4]] },
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const frames = collector.getData();
|
||||
expect(frames.length).toEqual(1);
|
||||
expect(frames[0]).toMatchInlineSnapshot(`
|
||||
StreamingDataFrame {
|
||||
"fields": Array [
|
||||
Object {
|
||||
"config": Object {},
|
||||
"labels": undefined,
|
||||
"name": "time",
|
||||
"type": "time",
|
||||
"values": Array [
|
||||
100,
|
||||
200,
|
||||
300,
|
||||
400,
|
||||
],
|
||||
},
|
||||
Object {
|
||||
"config": Object {},
|
||||
"labels": undefined,
|
||||
"name": "value",
|
||||
"type": "number",
|
||||
"values": Array [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
],
|
||||
},
|
||||
],
|
||||
"length": 4,
|
||||
"meta": undefined,
|
||||
"name": undefined,
|
||||
"options": Object {
|
||||
"maxDelta": Infinity,
|
||||
"maxLength": 600,
|
||||
},
|
||||
"refId": undefined,
|
||||
"timeFieldIndex": 0,
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
@ -1,86 +0,0 @@
|
||||
import { DataFrame, DataFrameJSON, StreamingDataFrame, StreamingFrameOptions } from '@grafana/data';
|
||||
import { MeasurementBatch, LiveMeasurements, MeasurementsQuery } from './types';
|
||||
|
||||
/**
|
||||
* This will collect
|
||||
*
|
||||
* @alpha -- experimental
|
||||
*/
|
||||
export class MeasurementCollector implements LiveMeasurements {
|
||||
measurements = new Map<string, StreamingDataFrame>();
|
||||
config: StreamingFrameOptions = {
|
||||
maxLength: 600, // Default capacity 10min @ 1hz
|
||||
};
|
||||
|
||||
//------------------------------------------------------
|
||||
// Public
|
||||
//------------------------------------------------------
|
||||
|
||||
getData(query?: MeasurementsQuery): DataFrame[] {
|
||||
const { key, fields } = query || {};
|
||||
|
||||
// Find the data
|
||||
let data: StreamingDataFrame[] = [];
|
||||
if (key) {
|
||||
const f = this.measurements.get(key);
|
||||
if (!f) {
|
||||
return [];
|
||||
}
|
||||
data.push(f);
|
||||
} else {
|
||||
// Add all frames
|
||||
for (const f of this.measurements.values()) {
|
||||
data.push(f);
|
||||
}
|
||||
}
|
||||
|
||||
// Filter the fields we want
|
||||
if (fields && fields.length) {
|
||||
let filtered: DataFrame[] = [];
|
||||
for (const frame of data) {
|
||||
const match = frame.fields.filter((f) => fields.includes(f.name));
|
||||
if (match.length > 0) {
|
||||
filtered.push({ ...frame, fields: match, length: frame.length }); // Copy the frame with fewer fields
|
||||
}
|
||||
}
|
||||
if (filtered.length) {
|
||||
return filtered;
|
||||
}
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
getKeys(): string[] {
|
||||
return Object.keys(this.measurements);
|
||||
}
|
||||
|
||||
ensureCapacity(size: number) {
|
||||
// TODO...
|
||||
}
|
||||
|
||||
//------------------------------------------------------
|
||||
// Collector
|
||||
//------------------------------------------------------
|
||||
|
||||
addBatch = (msg: MeasurementBatch) => {
|
||||
// HACK! sending one message from the backend, not a batch
|
||||
if (!msg.batch) {
|
||||
const df: DataFrameJSON = msg as any;
|
||||
msg = { batch: [df] };
|
||||
console.log('NOTE converting message to batch');
|
||||
}
|
||||
|
||||
for (const measure of msg.batch) {
|
||||
const key = measure.key ?? measure.schema?.name ?? '';
|
||||
|
||||
let s = this.measurements.get(key);
|
||||
if (s) {
|
||||
s.push(measure);
|
||||
} else {
|
||||
s = new StreamingDataFrame(measure, this.config); //
|
||||
this.measurements.set(key, s);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
};
|
||||
}
|
@ -1,3 +1 @@
|
||||
export * from './types';
|
||||
export * from './collector';
|
||||
export * from './query';
|
||||
|
@ -1,77 +1,114 @@
|
||||
import {
|
||||
DataFrame,
|
||||
DataFrameJSON,
|
||||
DataQueryResponse,
|
||||
isLiveChannelMessageEvent,
|
||||
isLiveChannelStatusEvent,
|
||||
isValidLiveChannelAddress,
|
||||
LiveChannelAddress,
|
||||
LiveChannelConnectionState,
|
||||
LiveChannelEvent,
|
||||
LoadingState,
|
||||
StreamingDataFrame,
|
||||
StreamingFrameOptions,
|
||||
} from '@grafana/data';
|
||||
import { LiveMeasurements, MeasurementsQuery } from './types';
|
||||
import { getGrafanaLiveSrv } from '../services/live';
|
||||
|
||||
import { Observable, of } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
import { toDataQueryError } from '../utils/queryResponse';
|
||||
|
||||
/**
|
||||
* @alpha -- experimental
|
||||
*/
|
||||
export function getLiveMeasurements(addr: LiveChannelAddress): LiveMeasurements | undefined {
|
||||
if (!isValidLiveChannelAddress(addr)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const live = getGrafanaLiveSrv();
|
||||
if (!live) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const channel = live.getChannel<LiveMeasurements>(addr);
|
||||
const getController = channel?.config?.getController;
|
||||
return getController ? getController() : undefined;
|
||||
export interface LiveDataFilter {
|
||||
fields?: string[];
|
||||
}
|
||||
|
||||
/**
|
||||
* When you know the stream will be managed measurements
|
||||
* @alpha
|
||||
*/
|
||||
export interface LiveDataStreamOptions {
|
||||
key?: string;
|
||||
addr: LiveChannelAddress;
|
||||
buffer?: StreamingFrameOptions;
|
||||
filter?: LiveDataFilter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Continue executing requests as long as `getNextQuery` returns a query
|
||||
*
|
||||
* @alpha -- experimental
|
||||
* @alpha
|
||||
*/
|
||||
export function getLiveMeasurementsObserver(
|
||||
addr: LiveChannelAddress,
|
||||
requestId: string,
|
||||
query?: MeasurementsQuery
|
||||
): Observable<DataQueryResponse> {
|
||||
const rsp: DataQueryResponse = { data: [] };
|
||||
if (!addr || !addr.path) {
|
||||
return of(rsp); // Address not configured yet
|
||||
export function getLiveDataStream(options: LiveDataStreamOptions): Observable<DataQueryResponse> {
|
||||
if (!isValidLiveChannelAddress(options.addr)) {
|
||||
return of({ error: toDataQueryError('invalid address'), data: [] });
|
||||
}
|
||||
|
||||
const live = getGrafanaLiveSrv();
|
||||
if (!live) {
|
||||
// This will only happen with the feature flag is not enabled
|
||||
rsp.error = { message: 'Grafana live is not initalized' };
|
||||
return of(rsp);
|
||||
return of({ error: toDataQueryError('grafana live is not initalized'), data: [] });
|
||||
}
|
||||
|
||||
rsp.key = requestId;
|
||||
return live
|
||||
.getChannel<LiveMeasurements>(addr)
|
||||
.getStream()
|
||||
.pipe(
|
||||
map((evt) => {
|
||||
if (isLiveChannelMessageEvent(evt)) {
|
||||
rsp.data = evt.message.getData(query);
|
||||
if (!rsp.data.length) {
|
||||
// ?? skip when data is empty ???
|
||||
return new Observable<DataQueryResponse>((subscriber) => {
|
||||
let data: StreamingDataFrame | undefined = undefined;
|
||||
let state = LoadingState.Loading;
|
||||
const { key, filter } = options;
|
||||
|
||||
const process = (msg: DataFrameJSON) => {
|
||||
if (!data) {
|
||||
data = new StreamingDataFrame(msg, options.buffer);
|
||||
} else {
|
||||
data.push(msg);
|
||||
}
|
||||
state = LoadingState.Streaming;
|
||||
|
||||
// TODO? this *coud* happen only when the schema changes
|
||||
let filtered = data as DataFrame;
|
||||
if (filter?.fields && filter.fields.length) {
|
||||
filtered = {
|
||||
...data,
|
||||
fields: data.fields.filter((f) => filter.fields!.includes(f.name)),
|
||||
};
|
||||
}
|
||||
|
||||
subscriber.next({ state, data: [filtered], key });
|
||||
};
|
||||
|
||||
const sub = live
|
||||
.getChannel<DataFrameJSON>(options.addr)
|
||||
.getStream()
|
||||
.subscribe({
|
||||
error: (err: any) => {
|
||||
state = LoadingState.Error;
|
||||
subscriber.next({ state, data: [data], key });
|
||||
sub.unsubscribe(); // close after error
|
||||
},
|
||||
complete: () => {
|
||||
if (state !== LoadingState.Error) {
|
||||
state = LoadingState.Done;
|
||||
}
|
||||
delete rsp.error;
|
||||
rsp.state = LoadingState.Streaming;
|
||||
} else if (isLiveChannelStatusEvent(evt)) {
|
||||
if (evt.error != null) {
|
||||
rsp.error = rsp.error;
|
||||
rsp.state = LoadingState.Error;
|
||||
subscriber.next({ state, data: [data], key });
|
||||
subscriber.complete();
|
||||
sub.unsubscribe();
|
||||
},
|
||||
next: (evt: LiveChannelEvent) => {
|
||||
if (isLiveChannelMessageEvent(evt)) {
|
||||
process(evt.message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
return { ...rsp }; // send event on all status messages
|
||||
})
|
||||
);
|
||||
if (isLiveChannelStatusEvent(evt)) {
|
||||
if (
|
||||
evt.state === LiveChannelConnectionState.Connected ||
|
||||
evt.state === LiveChannelConnectionState.Pending
|
||||
) {
|
||||
if (evt.message) {
|
||||
process(evt.message);
|
||||
}
|
||||
return;
|
||||
}
|
||||
console.log('ignore state', evt);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
return () => {
|
||||
sub.unsubscribe();
|
||||
};
|
||||
});
|
||||
}
|
||||
|
@ -1,32 +0,0 @@
|
||||
import { DataFrame, DataFrameJSON } from '@grafana/data';
|
||||
|
||||
/**
|
||||
* List of Measurements sent in a batch
|
||||
*
|
||||
* @alpha -- experimental
|
||||
*/
|
||||
export interface MeasurementBatch {
|
||||
/**
|
||||
* List of measurements to process
|
||||
*/
|
||||
batch: DataFrameJSON[];
|
||||
}
|
||||
|
||||
/**
|
||||
* @alpha -- experimental
|
||||
*/
|
||||
export interface MeasurementsQuery {
|
||||
key?: string;
|
||||
fields?: string[]; // only include the fields with these names
|
||||
}
|
||||
|
||||
/**
|
||||
* Channels that receive Measurements can collect them into frames
|
||||
*
|
||||
* @alpha -- experimental
|
||||
*/
|
||||
export interface LiveMeasurements {
|
||||
getData(query?: MeasurementsQuery): DataFrame[];
|
||||
getKeys(): string[];
|
||||
ensureCapacity(size: number): void;
|
||||
}
|
Reference in New Issue
Block a user