mirror of
https://github.com/grafana/grafana.git
synced 2025-01-13 09:32:12 -06:00
Zipkin: Use new trace data format (#31830)
* Use new trace api for zipkin * Fix datasource tests
This commit is contained in:
parent
f135d75a22
commit
def58f1f4a
@ -1,9 +1,9 @@
|
||||
/**
|
||||
* Type representing a tag in a trace span or fields of a log.
|
||||
*/
|
||||
export type TraceKeyValuePair = {
|
||||
export type TraceKeyValuePair<T = any> = {
|
||||
key: string;
|
||||
value: any;
|
||||
value: T;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -3,7 +3,7 @@ import { backendSrv } from 'app/core/services/backend_srv';
|
||||
import { of } from 'rxjs';
|
||||
import { createFetchResponse } from 'test/helpers/createFetchResponse';
|
||||
import { ZipkinDatasource } from './datasource';
|
||||
import { jaegerTrace, zipkinResponse } from './utils/testData';
|
||||
import { traceFrameFields, zipkinResponse } from './utils/testData';
|
||||
|
||||
jest.mock('@grafana/runtime', () => ({
|
||||
...((jest.requireActual('@grafana/runtime') as unknown) as object),
|
||||
@ -16,14 +16,14 @@ describe('ZipkinDatasource', () => {
|
||||
setupBackendSrv(zipkinResponse);
|
||||
const ds = new ZipkinDatasource(defaultSettings);
|
||||
await expect(ds.query({ targets: [{ query: '12345' }] } as any)).toEmitValuesWith((val) => {
|
||||
expect(val[0].data[0].fields[0].values.get(0)).toEqual(jaegerTrace);
|
||||
expect(val[0].data[0].fields).toMatchObject(traceFrameFields);
|
||||
});
|
||||
});
|
||||
it('runs query with traceId that includes special characters', async () => {
|
||||
setupBackendSrv(zipkinResponse);
|
||||
const ds = new ZipkinDatasource(defaultSettings);
|
||||
await expect(ds.query({ targets: [{ query: 'a/b' }] } as any)).toEmitValuesWith((val) => {
|
||||
expect(val[0].data[0].fields[0].values.get(0)).toEqual(jaegerTrace);
|
||||
expect(val[0].data[0].fields).toMatchObject(traceFrameFields);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -67,22 +67,7 @@ export class ZipkinDatasource extends DataSourceApi<ZipkinQuery> {
|
||||
|
||||
function responseToDataQueryResponse(response: { data: ZipkinSpan[] }): DataQueryResponse {
|
||||
return {
|
||||
data: [
|
||||
new MutableDataFrame({
|
||||
fields: [
|
||||
{
|
||||
name: 'trace',
|
||||
type: FieldType.trace,
|
||||
// There is probably better mapping than just putting everything in as a single value but that's how
|
||||
// we do it with jaeger and is the simplest right now.
|
||||
values: response?.data ? [transformResponse(response?.data)] : [],
|
||||
},
|
||||
],
|
||||
meta: {
|
||||
preferredVisualisationType: 'trace',
|
||||
},
|
||||
}),
|
||||
],
|
||||
data: response?.data ? [transformResponse(response?.data)] : [],
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { ArrayVector } from '@grafana/data';
|
||||
import { ZipkinSpan } from '../types';
|
||||
import { TraceResponse } from '../../jaeger/types';
|
||||
|
||||
export const zipkinResponse: ZipkinSpan[] = [
|
||||
{
|
||||
@ -59,128 +59,65 @@ export const zipkinResponse: ZipkinSpan[] = [
|
||||
},
|
||||
];
|
||||
|
||||
export const jaegerTrace: TraceResponse = {
|
||||
processes: {
|
||||
'service 1': {
|
||||
serviceName: 'service 1',
|
||||
tags: [
|
||||
{
|
||||
key: 'ipv4',
|
||||
type: 'string',
|
||||
value: '1.0.0.1',
|
||||
},
|
||||
{
|
||||
key: 'port',
|
||||
type: 'number',
|
||||
value: 42,
|
||||
},
|
||||
export const traceFrameFields = [
|
||||
{ name: 'traceID', values: ['trace_id', 'trace_id', 'trace_id'] },
|
||||
{ name: 'spanID', values: ['span 1 id', 'span 2 id', 'span 3 id'] },
|
||||
{ name: 'parentSpanID', values: [undefined, 'span 1 id', 'span 1 id'] },
|
||||
{ name: 'operationName', values: ['span 1', 'span 2', 'span 3'] },
|
||||
{ name: 'serviceName', values: ['service 1', 'service 2', 'spanstore-jdbc'] },
|
||||
{
|
||||
name: 'serviceTags',
|
||||
values: [
|
||||
[
|
||||
{ key: 'ipv4', value: '1.0.0.1' },
|
||||
{ key: 'port', value: 42 },
|
||||
],
|
||||
},
|
||||
'service 2': {
|
||||
serviceName: 'service 2',
|
||||
tags: [
|
||||
{
|
||||
key: 'ipv4',
|
||||
type: 'string',
|
||||
value: '1.0.0.1',
|
||||
},
|
||||
],
|
||||
},
|
||||
'spanstore-jdbc': {
|
||||
serviceName: 'spanstore-jdbc',
|
||||
tags: [
|
||||
{
|
||||
key: 'ipv6',
|
||||
type: 'string',
|
||||
value: '127.0.0.1',
|
||||
},
|
||||
],
|
||||
},
|
||||
[{ key: 'ipv4', value: '1.0.0.1' }],
|
||||
[{ key: 'ipv6', value: '127.0.0.1' }],
|
||||
],
|
||||
},
|
||||
traceID: 'trace_id',
|
||||
warnings: null,
|
||||
spans: [
|
||||
{
|
||||
duration: 10,
|
||||
flags: 1,
|
||||
logs: [
|
||||
{ name: 'startTime', values: [0.001, 0.004, 0.006] },
|
||||
{ name: 'duration', values: [0.01, 0.005, 0.007] },
|
||||
{
|
||||
name: 'logs',
|
||||
values: [
|
||||
[
|
||||
{
|
||||
timestamp: 2,
|
||||
fields: [{ key: 'annotation', type: 'string', value: 'annotation text' }],
|
||||
fields: [
|
||||
{
|
||||
key: 'annotation',
|
||||
value: 'annotation text',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
timestamp: 6,
|
||||
fields: [{ key: 'annotation', type: 'string', value: 'annotation text 3' }],
|
||||
fields: [
|
||||
{
|
||||
key: 'annotation',
|
||||
value: 'annotation text 3',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
operationName: 'span 1',
|
||||
processID: 'service 1',
|
||||
startTime: 1,
|
||||
spanID: 'span 1 id',
|
||||
traceID: 'trace_id',
|
||||
warnings: null as any,
|
||||
tags: [
|
||||
{
|
||||
key: 'kind',
|
||||
type: 'string',
|
||||
value: 'CLIENT',
|
||||
},
|
||||
{
|
||||
key: 'tag1',
|
||||
type: 'string',
|
||||
value: 'val1',
|
||||
},
|
||||
{
|
||||
key: 'tag2',
|
||||
type: 'string',
|
||||
value: 'val2',
|
||||
},
|
||||
[],
|
||||
[],
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'tags',
|
||||
values: [
|
||||
[
|
||||
{ key: 'kind', value: 'CLIENT' },
|
||||
{ key: 'tag1', value: 'val1' },
|
||||
{ key: 'tag2', value: 'val2' },
|
||||
],
|
||||
references: [],
|
||||
},
|
||||
{
|
||||
duration: 5,
|
||||
flags: 1,
|
||||
logs: [],
|
||||
operationName: 'span 2',
|
||||
processID: 'service 2',
|
||||
startTime: 4,
|
||||
spanID: 'span 2 id',
|
||||
traceID: 'trace_id',
|
||||
warnings: null as any,
|
||||
tags: [
|
||||
{
|
||||
key: 'error',
|
||||
type: 'bool',
|
||||
value: true,
|
||||
},
|
||||
[
|
||||
{ key: 'error', value: true },
|
||||
{ key: 'errorValue', value: '404' },
|
||||
],
|
||||
references: [
|
||||
{
|
||||
refType: 'CHILD_OF',
|
||||
spanID: 'span 1 id',
|
||||
traceID: 'trace_id',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
duration: 7,
|
||||
flags: 1,
|
||||
logs: [],
|
||||
operationName: 'span 3',
|
||||
processID: 'spanstore-jdbc',
|
||||
startTime: 6,
|
||||
tags: [],
|
||||
spanID: 'span 3 id',
|
||||
traceID: 'trace_id',
|
||||
warnings: null as any,
|
||||
references: [
|
||||
{
|
||||
refType: 'CHILD_OF',
|
||||
spanID: 'span 1 id',
|
||||
traceID: 'trace_id',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
[],
|
||||
],
|
||||
},
|
||||
].map((f) => ({ ...f, values: new ArrayVector<any>(f.values) }));
|
||||
|
@ -1,8 +1,10 @@
|
||||
import { transformResponse } from './transforms';
|
||||
import { jaegerTrace, zipkinResponse } from './testData';
|
||||
import { traceFrameFields, zipkinResponse } from './testData';
|
||||
|
||||
describe('transformResponse', () => {
|
||||
it('transforms response', () => {
|
||||
expect(transformResponse(zipkinResponse)).toEqual(jaegerTrace);
|
||||
const dataFrame = transformResponse(zipkinResponse);
|
||||
|
||||
expect(dataFrame.fields).toMatchObject(traceFrameFields);
|
||||
});
|
||||
});
|
||||
|
@ -1,115 +1,114 @@
|
||||
import { identity, keyBy } from 'lodash';
|
||||
import { ZipkinAnnotation, ZipkinEndpoint, ZipkinSpan } from '../types';
|
||||
import * as Jaeger from '../../jaeger/types';
|
||||
import { identity } from 'lodash';
|
||||
import { ZipkinAnnotation, ZipkinSpan } from '../types';
|
||||
import { DataFrame, FieldType, MutableDataFrame, TraceKeyValuePair, TraceLog, TraceSpanRow } from '@grafana/data';
|
||||
|
||||
/**
|
||||
* Transforms response to format similar to Jaegers as we use Jaeger ui on the frontend.
|
||||
* Transforms response to Grafana trace data frame.
|
||||
*/
|
||||
export function transformResponse(zSpans: ZipkinSpan[]): Jaeger.TraceResponse {
|
||||
return {
|
||||
processes: gatherProcesses(zSpans),
|
||||
traceID: zSpans[0].traceId,
|
||||
spans: zSpans.map(transformSpan),
|
||||
warnings: null,
|
||||
};
|
||||
export function transformResponse(zSpans: ZipkinSpan[]): DataFrame {
|
||||
const spanRows = zSpans.map(transformSpan);
|
||||
const frame = new MutableDataFrame({
|
||||
fields: [
|
||||
{ name: 'traceID', type: FieldType.string },
|
||||
{ name: 'spanID', type: FieldType.string },
|
||||
{ name: 'parentSpanID', type: FieldType.string },
|
||||
{ name: 'operationName', type: FieldType.string },
|
||||
{ name: 'serviceName', type: FieldType.string },
|
||||
{ name: 'serviceTags', type: FieldType.other },
|
||||
{ name: 'startTime', type: FieldType.number },
|
||||
{ name: 'duration', type: FieldType.number },
|
||||
{ name: 'logs', type: FieldType.other },
|
||||
{ name: 'tags', type: FieldType.other },
|
||||
],
|
||||
meta: {
|
||||
preferredVisualisationType: 'trace',
|
||||
},
|
||||
});
|
||||
|
||||
for (const span of spanRows) {
|
||||
frame.add(span);
|
||||
}
|
||||
|
||||
return frame;
|
||||
}
|
||||
|
||||
function transformSpan(span: ZipkinSpan): Jaeger.Span {
|
||||
const jaegerSpan: Jaeger.Span = {
|
||||
duration: span.duration,
|
||||
// TODO: not sure what this is
|
||||
flags: 1,
|
||||
logs: span.annotations?.map(transformAnnotation) ?? [],
|
||||
operationName: span.name,
|
||||
processID: span.localEndpoint?.serviceName || span.remoteEndpoint?.serviceName || 'unknown',
|
||||
startTime: span.timestamp,
|
||||
spanID: span.id,
|
||||
function transformSpan(span: ZipkinSpan): TraceSpanRow {
|
||||
const row = {
|
||||
traceID: span.traceId,
|
||||
warnings: null as any,
|
||||
tags: Object.keys(span.tags || {}).map((key) => {
|
||||
// If tag is error we remap it to simple boolean so that the Jaeger ui will show an error icon.
|
||||
return {
|
||||
key,
|
||||
type: key === 'error' ? 'bool' : 'string',
|
||||
value: key === 'error' ? true : span.tags![key],
|
||||
};
|
||||
}),
|
||||
references: span.parentId
|
||||
? [
|
||||
{
|
||||
refType: 'CHILD_OF',
|
||||
spanID: span.parentId,
|
||||
traceID: span.traceId,
|
||||
},
|
||||
]
|
||||
: [],
|
||||
spanID: span.id,
|
||||
parentSpanID: span.parentId,
|
||||
operationName: span.name,
|
||||
serviceName: span.localEndpoint?.serviceName || span.remoteEndpoint?.serviceName || 'unknown',
|
||||
serviceTags: serviceTags(span),
|
||||
startTime: span.timestamp / 1000,
|
||||
duration: span.duration / 1000,
|
||||
logs: span.annotations?.map(transformAnnotation) ?? [],
|
||||
tags: Object.keys(span.tags || {}).reduce<TraceKeyValuePair[]>((acc, key) => {
|
||||
// If tag is error we remap it to simple boolean so that the trace ui will show an error icon.
|
||||
if (key === 'error') {
|
||||
acc.push({
|
||||
key: 'error',
|
||||
value: true,
|
||||
});
|
||||
|
||||
acc.push({
|
||||
key: 'errorValue',
|
||||
value: span.tags!['error'],
|
||||
});
|
||||
return acc;
|
||||
}
|
||||
acc.push({ key, value: span.tags![key] });
|
||||
return acc;
|
||||
}, []),
|
||||
};
|
||||
|
||||
if (span.kind) {
|
||||
jaegerSpan.tags = [
|
||||
row.tags = [
|
||||
{
|
||||
key: 'kind',
|
||||
type: 'string',
|
||||
value: span.kind,
|
||||
},
|
||||
...(jaegerSpan.tags ?? []),
|
||||
...(row.tags ?? []),
|
||||
];
|
||||
}
|
||||
|
||||
return jaegerSpan;
|
||||
return row;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps annotations as a Jaeger log as that seems to be the closest thing.
|
||||
* Maps annotations as a log as that seems to be the closest thing.
|
||||
* See https://zipkin.io/zipkin-api/#/default/get_trace__traceId_
|
||||
*/
|
||||
function transformAnnotation(annotation: ZipkinAnnotation): Jaeger.TraceLog {
|
||||
function transformAnnotation(annotation: ZipkinAnnotation): TraceLog {
|
||||
return {
|
||||
timestamp: annotation.timestamp,
|
||||
fields: [
|
||||
{
|
||||
key: 'annotation',
|
||||
type: 'string',
|
||||
value: annotation.value,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
function gatherProcesses(zSpans: ZipkinSpan[]): Record<string, Jaeger.TraceProcess> {
|
||||
const processes = zSpans.reduce((acc, span) => {
|
||||
if (span.localEndpoint) {
|
||||
acc.push(endpointToProcess(span.localEndpoint));
|
||||
}
|
||||
if (span.remoteEndpoint) {
|
||||
acc.push(endpointToProcess(span.remoteEndpoint));
|
||||
}
|
||||
return acc;
|
||||
}, [] as Jaeger.TraceProcess[]);
|
||||
return keyBy(processes, 'serviceName');
|
||||
function serviceTags(span: ZipkinSpan): TraceKeyValuePair[] {
|
||||
const endpoint = span.localEndpoint || span.remoteEndpoint;
|
||||
if (!endpoint) {
|
||||
return [];
|
||||
}
|
||||
return [
|
||||
valueToTag('ipv4', endpoint.ipv4),
|
||||
valueToTag('ipv6', endpoint.ipv6),
|
||||
valueToTag('port', endpoint.port),
|
||||
].filter(identity) as TraceKeyValuePair[];
|
||||
}
|
||||
|
||||
function endpointToProcess(endpoint: ZipkinEndpoint): Jaeger.TraceProcess {
|
||||
return {
|
||||
serviceName: endpoint.serviceName,
|
||||
tags: [
|
||||
valueToTag('ipv4', endpoint.ipv4, 'string'),
|
||||
valueToTag('ipv6', endpoint.ipv6, 'string'),
|
||||
valueToTag('port', endpoint.port, 'number'),
|
||||
].filter(identity) as Jaeger.TraceKeyValuePair[],
|
||||
};
|
||||
}
|
||||
|
||||
function valueToTag(
|
||||
key: string,
|
||||
value: string | number | undefined,
|
||||
type: string
|
||||
): Jaeger.TraceKeyValuePair | undefined {
|
||||
function valueToTag<T>(key: string, value: T): TraceKeyValuePair<T> | undefined {
|
||||
if (!value) {
|
||||
return undefined;
|
||||
}
|
||||
return {
|
||||
key,
|
||||
type,
|
||||
value,
|
||||
};
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user