diff --git a/packages/grafana-data/src/types/trace.ts b/packages/grafana-data/src/types/trace.ts index d5ab6166df6..363584adab7 100644 --- a/packages/grafana-data/src/types/trace.ts +++ b/packages/grafana-data/src/types/trace.ts @@ -1,9 +1,9 @@ /** * Type representing a tag in a trace span or fields of a log. */ -export type TraceKeyValuePair = { +export type TraceKeyValuePair = { key: string; - value: any; + value: T; }; /** diff --git a/public/app/plugins/datasource/zipkin/datasource.test.ts b/public/app/plugins/datasource/zipkin/datasource.test.ts index ac3767c7fc1..593817e7cd1 100644 --- a/public/app/plugins/datasource/zipkin/datasource.test.ts +++ b/public/app/plugins/datasource/zipkin/datasource.test.ts @@ -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); }); }); }); diff --git a/public/app/plugins/datasource/zipkin/datasource.ts b/public/app/plugins/datasource/zipkin/datasource.ts index a8bd41ee2b7..69a15d53b2c 100644 --- a/public/app/plugins/datasource/zipkin/datasource.ts +++ b/public/app/plugins/datasource/zipkin/datasource.ts @@ -67,22 +67,7 @@ export class ZipkinDatasource extends DataSourceApi { 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)] : [], }; } diff --git a/public/app/plugins/datasource/zipkin/utils/testData.ts b/public/app/plugins/datasource/zipkin/utils/testData.ts index 8143b937ed6..5bd03d83c66 100644 --- a/public/app/plugins/datasource/zipkin/utils/testData.ts +++ b/public/app/plugins/datasource/zipkin/utils/testData.ts @@ -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(f.values) })); diff --git a/public/app/plugins/datasource/zipkin/utils/transforms.test.ts b/public/app/plugins/datasource/zipkin/utils/transforms.test.ts index e49bac79d8d..99bea7c61c8 100644 --- a/public/app/plugins/datasource/zipkin/utils/transforms.test.ts +++ b/public/app/plugins/datasource/zipkin/utils/transforms.test.ts @@ -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); }); }); diff --git a/public/app/plugins/datasource/zipkin/utils/transforms.ts b/public/app/plugins/datasource/zipkin/utils/transforms.ts index c3df2456513..db193e68fe3 100644 --- a/public/app/plugins/datasource/zipkin/utils/transforms.ts +++ b/public/app/plugins/datasource/zipkin/utils/transforms.ts @@ -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((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 { - 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(key: string, value: T): TraceKeyValuePair | undefined { if (!value) { return undefined; } return { key, - type, value, }; }