Zipkin: Use new trace data format (#31830)

* Use new trace api for zipkin

* Fix datasource tests
This commit is contained in:
Andrej Ocenas 2021-03-10 17:45:03 +01:00 committed by GitHub
parent f135d75a22
commit def58f1f4a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 135 additions and 212 deletions

View File

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

View File

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

View File

@ -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)] : [],
};
}

View File

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

View File

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

View File

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