123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188 |
- import { identity } from 'lodash';
- import { DataFrame, FieldType, MutableDataFrame, TraceKeyValuePair, TraceLog, TraceSpanRow } from '@grafana/data';
- import { ZipkinAnnotation, ZipkinEndpoint, ZipkinSpan } from '../types';
- /**
- * Transforms response to Grafana trace data frame.
- */
- 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',
- custom: {
- traceFormat: 'zipkin',
- },
- },
- });
- for (const span of spanRows) {
- frame.add(span);
- }
- return frame;
- }
- function transformSpan(span: ZipkinSpan): TraceSpanRow {
- const row = {
- 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) {
- row.tags = [
- {
- key: 'kind',
- value: span.kind,
- },
- ...(row.tags ?? []),
- ];
- }
- if (span.shared) {
- row.tags = [
- {
- key: 'shared',
- value: span.shared,
- },
- ...(row.tags ?? []),
- ];
- }
- return row;
- }
- /**
- * 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): TraceLog {
- return {
- timestamp: annotation.timestamp,
- fields: [
- {
- key: 'annotation',
- value: annotation.value,
- },
- ],
- };
- }
- 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),
- valueToTag('endpointType', span.localEndpoint ? 'local' : 'remote'),
- ].filter(identity) as TraceKeyValuePair[];
- }
- function valueToTag<T>(key: string, value: T): TraceKeyValuePair<T> | undefined {
- if (!value) {
- return undefined;
- }
- return {
- key,
- value,
- };
- }
- /**
- * Transforms data frame to Zipkin response
- */
- export const transformToZipkin = (data: MutableDataFrame): ZipkinSpan[] => {
- let response: ZipkinSpan[] = [];
- for (let i = 0; i < data.length; i++) {
- const span = data.get(i);
- response.push({
- traceId: span.traceID,
- parentId: span.parentSpanID,
- name: span.operationName,
- id: span.spanID,
- timestamp: span.startTime * 1000,
- duration: span.duration * 1000,
- ...getEndpoint(span),
- annotations: span.logs.length
- ? span.logs.map((l: TraceLog) => ({ timestamp: l.timestamp, value: l.fields[0].value }))
- : undefined,
- tags: span.tags.length
- ? span.tags
- .filter((t: TraceKeyValuePair) => t.key !== 'kind' && t.key !== 'endpointType' && t.key !== 'shared')
- .reduce((tags: { [key: string]: string }, t: TraceKeyValuePair) => {
- if (t.key === 'error') {
- return {
- ...tags,
- [t.key]: span.tags.find((t: TraceKeyValuePair) => t.key === 'errorValue').value || '',
- };
- }
- return { ...tags, [t.key]: t.value };
- }, {})
- : undefined,
- kind: span.tags.find((t: TraceKeyValuePair) => t.key === 'kind')?.value,
- shared: span.tags.find((t: TraceKeyValuePair) => t.key === 'shared')?.value,
- });
- }
- return response;
- };
- // Returns remote or local endpoint object
- const getEndpoint = (span: any): { [key: string]: ZipkinEndpoint } | undefined => {
- const key =
- span.serviceTags.find((t: TraceKeyValuePair) => t.key === 'endpointType')?.value === 'local'
- ? 'localEndpoint'
- : 'remoteEndpoint';
- return span.serviceName !== 'unknown'
- ? {
- [key]: {
- serviceName: span.serviceName,
- ipv4: span.serviceTags.find((t: TraceKeyValuePair) => t.key === 'ipv4')?.value,
- ipv6: span.serviceTags.find((t: TraceKeyValuePair) => t.key === 'ipv6')?.value,
- port: span.serviceTags.find((t: TraceKeyValuePair) => t.key === 'port')?.value,
- },
- }
- : undefined;
- };
|