Live: remove measurement controller (#32622)

This commit is contained in:
Ryan McKinley
2021-04-01 22:32:56 -07:00
committed by GitHub
parent db12818d25
commit d2afcdd415
16 changed files with 238 additions and 372 deletions

View File

@ -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,
}
`);
});
});

View File

@ -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;
};
}

View File

@ -1,3 +1 @@
export * from './types';
export * from './collector';
export * from './query';

View File

@ -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();
};
});
}

View File

@ -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;
}